Fork of daniellemaywood.uk/gleam — Wasm codegen work
Something went wrong. Try again.
185 kB · 5329 lines
Rust
at wasm
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2019 The Gleam contributors
#![allow(clippy::unwrap_used, clippy::expect_used)]use crate::ast::{self, SrcSpan};use crate::bit_array::UnsupportedOption;use crate::build::{Origin, Outcome, Runtime, Target};use crate::dependency::{PackageFetcher, ResolutionError};use crate::diagnostic::{Diagnostic, ExtraLabel, Label, Location};
use crate::derivation_tree::DerivationTreePrinter;use crate::parse::error::ParseErrorDetails;use crate::strings::{to_snake_case, to_upper_camel_case};use crate::type_::collapse_links;use crate::type_::error::{ IncorrectArityContext, InvalidImportKind, MissingAnnotation, ModuleValueUsageContext, Named, RecordField, UnexpectedLabelledArgKind, UnknownField, UnknownTypeHint, UnsafeRecordUpdateReason,};use crate::type_::printer::{Names, Printer};use crate::type_::{FieldAccessUsage, error::PatternMatchKind};use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type};use crate::{bit_array, diagnostic::Level, type_::UnifyErrorSituation};use ecow::EcoString;use hexpm::version::Version;use itertools::Itertools;use std::borrow::Cow;use std::collections::HashMap;use std::fmt::Debug;use std::io::Write;use std::path::PathBuf;use std::sync::Arc;use termcolor::Buffer;use thiserror::Error;use vec1::Vec1;
use camino::{Utf8Path, Utf8PathBuf};
pub type Name = EcoString;
pub type Result<Ok, Err = Error> = std::result::Result<Ok, Err>;
#[cfg(test)]pub mod tests;
macro_rules! wrap_format { ($($tts:tt)*) => { wrap(&format!($($tts)*)) }}pub(crate) use wrap_format;
#[derive(Debug, Clone, Eq, PartialEq)]pub struct UnknownImportDetails { pub module: Name, pub location: SrcSpan, pub path: Utf8PathBuf, pub src: EcoString, pub modules: Vec<EcoString>,}
#[derive(Debug, Clone, Eq, PartialEq)]pub struct ImportCycleLocationDetails { pub location: SrcSpan, pub path: Utf8PathBuf, pub src: EcoString,}
/// Describes where a defined module comes from.#[derive(Debug, Clone, Eq, PartialEq)]pub struct DefinedModuleOrigin { /// The package defining the module pub package_name: EcoString, /// The path to the module pub path: Utf8PathBuf,}
/// A module for which type checking failed.#[derive(Debug, Eq, PartialEq, Error, Clone)]#[error("type checking failed")]pub struct FailedModule { pub path: Utf8PathBuf, pub src: EcoString, pub errors: Vec1<crate::type_::Error>, pub names: Box<Names>,}
#[derive(Debug, Eq, PartialEq, Error, Clone)]#[error("module couldn't be analysed")]pub struct SkippedModule { pub path: Utf8PathBuf, pub code: EcoString, pub name: EcoString, /// The location where the module is importing a dependency that (directly /// or indirectly) was skipped. pub location: SrcSpan, pub reason: SkipReason,}
#[derive(Debug, Eq, PartialEq, Clone)]pub enum SkipReason { /// The imported module has an error. DependencyHasError { name: EcoString }, /// The imported module was skipped too, because it ultimately depends on /// some module with an error. DependencyWasSkipped { name: EcoString, /// The name of the module that does have an error, making it so this /// one cannot be compiled. erroring_module: EcoString, },}
impl SkipReason { /// Returns the name of the module whose errors made it so the skipped /// module couldn't be compiled. pub fn erroring_module(&self) -> EcoString { match self { SkipReason::DependencyHasError { name } => name.clone(), SkipReason::DependencyWasSkipped { erroring_module, .. } => erroring_module.clone(), } }}
#[derive(Debug, Eq, PartialEq, Error, Clone)]pub enum Error { #[error("failed to parse Gleam source code")] Parse { path: Utf8PathBuf, src: EcoString, error: Box<crate::parse::error::ParseError>, },
#[error("type checking failed")] Type { /// All the modules that were skipped because they depend (directly or /// indirectly) on some other module that has an error. skipped_modules: Vec<SkippedModule>, /// All the modules that were analysed and had an error. /// This maps from module name to information about the module. failed_modules: HashMap<EcoString, FailedModule>, },
#[error("unknown import {import}")] UnknownImport { import: EcoString, // Boxed to prevent this variant from being overly large details: Box<UnknownImportDetails>, },
#[error("duplicate module {module}")] DuplicateModule { module: Name, first: DefinedModuleOrigin, second: DefinedModuleOrigin, },
#[error("duplicate source file {file}")] DuplicateSourceFile { file: String },
#[error("duplicate native Erlang module {module}")] DuplicateNativeErlangModule { module: Name, first: Utf8PathBuf, second: Utf8PathBuf, },
#[error("gleam module {module} clashes with native file of same name")] ClashingGleamModuleAndNativeFileName { module: Name, gleam_file: Utf8PathBuf, native_file: Utf8PathBuf, },
#[error("cyclical module imports")] ImportCycle { modules: Vec1<(EcoString, ImportCycleLocationDetails)>, },
#[error("cyclical package dependencies")] PackageCycle { packages: Vec<EcoString> },
#[error("{action:?} {path:?} failed: {err:?}")] FileIo { kind: FileKind, action: FileIoAction, path: Utf8PathBuf, err: Option<String>, },
#[error("Non Utf-8 Path: {path}")] NonUtf8Path { path: PathBuf },
#[error("{error}")] GitInitialization { error: String },
#[error("io operation failed")] StandardIo { action: StandardIoAction, err: Option<std::io::ErrorKind>, },
#[error("source code incorrectly formatted")] Format { problem_files: Vec<Unformatted> },
#[error("Hex error: {0}")] Hex(String),
#[error("{error}")] ExpandTar { error: String },
#[error("{err}")] AddTar { path: Utf8PathBuf, err: String },
#[error("{0}")] TarFinish(String),
#[error("{error}")] ZipAdd { path: String, error: String },
#[error("{0}")] ZipFinish(String),
#[error("{0}")] Gzip(String),
#[error("shell program `{program}` not found")] ShellProgramNotFound { program: String, os: OS },
#[error("shell program `{program}` failed")] ShellCommand { program: String, reason: ShellCommandFailureReason, },
#[error("{name} is not a valid project name")] InvalidProjectName { name: String, reason: InvalidProjectNameReason, },
#[error("{module} is not a valid module name")] InvalidModuleName { module: String },
#[error("{module} is not module")] ModuleDoesNotExist { module: EcoString, suggestion: Option<EcoString>, },
#[error("{module} does not have a main function")] ModuleDoesNotHaveMainFunction { module: EcoString, origin: Origin },
#[error("{module} does not have a public main function")] MainFunctionIsPrivate { module: EcoString },
#[error("{module}'s main function has the wrong arity so it can not be run")] MainFunctionHasWrongArity { module: EcoString, arity: usize },
#[error("{module}'s main function does not support the current target")] MainFunctionDoesNotSupportTarget { module: EcoString, target: Target },
#[error("{input} is not a valid version. {error}")] InvalidVersionFormat { input: String, error: String },
#[error("incompatible locked version. {error}")] IncompatibleLockedVersion { error: String },
#[error("project root already exists")] ProjectRootAlreadyExist { path: String },
#[error("File(s) already exist in {}",file_names.iter().map(|x| x.as_str()).join(", "))] OutputFilesAlreadyExist { file_names: Vec<Utf8PathBuf> },
#[error("Packages not exist: {}", packages.iter().join(", "))] RemovedPackagesNotExist { packages: Vec<String> },
#[error("Packages to update not exist: {}", packages.iter().join(", "))] PackagesToUpdateNotExist { packages: Vec<EcoString> },
#[error("unable to find project root")] UnableToFindProjectRoot { path: String },
#[error("gleam.toml version {toml_ver} does not match .app version {app_ver}")] VersionDoesNotMatch { toml_ver: String, app_ver: String },
#[error("warnings are not permitted")] ForbiddenWarnings { count: usize },
#[error("Invalid runtime for target {target:?}: {invalid_runtime:?}")] InvalidRuntime { target: Target, invalid_runtime: Runtime, },
#[error("package downloading failed: {error}")] DownloadPackageError { package_name: String, package_version: String, error: String, },
#[error("{0}")] Http(String),
#[error("Failed to create canonical path for package {0}")] DependencyCanonicalizationFailed(String),
#[error("Could not find versions that satisfy dependency requirements")] DependencyResolutionNoSolution { root_package_name: EcoString, derivation_tree: Box<NeverEqual<pubgrub::DerivationTree<String, pubgrub::Ranges<Version>, String>>>, },
#[error("Dependency resolution failed: {0}")] DependencyResolutionError(String),
#[error("The package {0} is listed in dependencies and dev_dependencies")] DuplicateDependency(EcoString),
#[error("Expected package {expected} at path {path} but found {found} instead")] WrongDependencyProvided { path: Utf8PathBuf, expected: String, found: String, },
#[error("The package {package} is provided multiple times, as {source_1} and {source_2}")] ProvidedDependencyConflict { package: String, source_1: String, source_2: String, },
#[error("The path {path} does not exist in the git repository {repo} for package {package}")] GitDependencyPathNotFound { package: String, path: String, repo: String, },
#[error("The package was missing required fields for publishing")] MissingHexPublishFields { description_missing: bool, licence_missing: bool, },
#[error("Dependency {package:?} has not been published to Hex")] PublishNonHexDependencies { package: String },
#[error("The package {package} uses unsupported build tools {build_tools:?}")] UnsupportedBuildTool { package: String, build_tools: Vec<EcoString>, },
#[error("Opening docs at {path} failed: {error}")] FailedToOpenDocs { path: Utf8PathBuf, error: String },
#[error( "The package {package} requires a Gleam version satisfying \{required_version} and you are using v{gleam_version}" )] IncompatibleCompilerVersion { package: String, required_version: String, gleam_version: String, },
#[error("The --javascript-prelude flag must be given when compiling to JavaScript")] JavaScriptPreludeRequired,
#[error("The modules {unfinished:?} contain todo expressions and so cannot be published")] CannotPublishTodo { unfinished: Vec<EcoString> },
#[error("The modules {unfinished:?} contain todo expressions and so cannot be published")] CannotPublishEcho { unfinished: Vec<EcoString> },
#[error( "The modules {unfinished:?} contain internal types in their public API so cannot be published" )] CannotPublishLeakedInternalType { unfinished: Vec<EcoString> },
#[error("The modules {unfinished:?} are empty and so cannot be published")] CannotPublishEmptyModules { unfinished: Vec<EcoString> },
#[error("Publishing packages with an invalid README is not permitted")] CannotPublishWithInvalidReadme { reason: InvalidReadmeReason },
#[error("Publishing packages to reserve names is not permitted")] HexPackageSquatting,
#[error("The package includes the default main function so cannot be published")] CannotPublishWithDefaultMain { package_name: EcoString },
#[error("Corrupt manifest.toml")] CorruptManifest,
#[error("The Gleam module {path} would overwrite the Erlang module {name}")] GleamModuleWouldOverwriteStandardErlangModule { name: EcoString, path: Utf8PathBuf },
#[error("Version already published")] HexPublishReplaceRequired { version: String },
#[error("Insufficient permissions to publish {name} {version}")] HexPublishAccessDenied { name: String, version: String },
#[error("The gleam version constraint is wrong and so cannot be published")] CannotPublishWrongVersion { minimum_required_version: SmallVersion, wrongfully_allowed_version: SmallVersion, },
#[error("Failed to encrypt local Hex API key")] FailedToEncryptLocalHexApiKey { detail: String },
#[error("Failed to decrypt local Hex API key")] FailedToDecryptLocalHexApiKey,
#[error("Cannot add a package with the same name as a dependency")] CannotAddSelfAsDependency { name: EcoString },
#[error("Hex session revoked")] HexSessionRevoked,
#[error("Incorrect Hex one-time-password")] IncorrectHexOneTimePassword,
#[error("{path} could not be added to the tarball as it is outside the project root")] TarPathOutsideOfProjectRoot { path: Utf8PathBuf },
#[error("could not create temp file: {error}")] CouldNotCreateTempFile { error: String },}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]pub enum InvalidReadmeReason { Missing, Empty, Default,}
// A wrapper that ignores the inner value for equality:#[derive(Debug, Clone)]pub struct NeverEqual<T>(pub T);
impl<T> PartialEq for NeverEqual<T> { fn eq(&self, _other: &Self) -> bool { false }}impl<T> Eq for NeverEqual<T> {}
/// This is to make clippy happy and not make the error variant too big by/// storing an entire `hexpm::version::Version` in the error.////// This is enough to report wrong Gleam compiler versions.///#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub struct SmallVersion { major: u8, minor: u8, patch: u8,}
impl SmallVersion { pub fn from_hexpm(version: Version) -> Self { Self { major: version.major as u8, minor: version.minor as u8, patch: version.patch as u8, } }
pub fn to_semver_string(&self) -> String { format!("{}.{}.{}", self.major, self.minor, self.patch) }}#[derive(Debug, Clone, Eq, PartialEq, Copy)]pub enum OS { Linux(Distro), MacOS, Windows, Other,}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]pub enum Distro { Ubuntu, Debian, Other,}
pub fn parse_os(os: &str, distro: &str) -> OS { match os { "macos" => OS::MacOS, "windows" => OS::Windows, "linux" => OS::Linux(parse_linux_distribution(distro)), _ => OS::Other, }}
pub fn parse_linux_distribution(distro: &str) -> Distro { match distro { "ubuntu" => Distro::Ubuntu, "debian" => Distro::Debian, _ => Distro::Other, }}
#[derive(Debug, Eq, PartialEq, Clone)]pub enum ShellCommandFailureReason { /// When we don't have any context about the failure Unknown, /// When the actual running of the command failed for some reason. IoError(std::io::ErrorKind), /// When the shell command returned an error status ShellCommandError(String),}
impl Error { pub fn http<E>(error: E) -> Error where E: std::error::Error, { Self::Http(error.to_string()) }
pub fn hex(error: hexpm::ApiError) -> Error { match error { hexpm::ApiError::Json(_) | hexpm::ApiError::Io(_) | hexpm::ApiError::RateLimited | hexpm::ApiError::InvalidCredentials | hexpm::ApiError::UnexpectedResponse(_, _) | hexpm::ApiError::InvalidPackageNameFormat(_) | hexpm::ApiError::IncorrectPayloadSignature | hexpm::ApiError::InvalidProtobuf(_) | hexpm::ApiError::InvalidVersionFormat(_) | hexpm::ApiError::NotFound | hexpm::ApiError::InvalidVersionRequirementFormat(_) | hexpm::ApiError::IncorrectChecksum | hexpm::ApiError::Forbidden | hexpm::ApiError::NotReplacing | hexpm::ApiError::LateModification | hexpm::ApiError::LateDeletion | hexpm::ApiError::OAuthTimeout | hexpm::ApiError::OAuthAccessDenied | hexpm::ApiError::ExpiredToken => Self::Hex(error.to_string()),
hexpm::ApiError::OAuthRefreshTokenRejected => Self::HexSessionRevoked,
hexpm::ApiError::IncorrectOneTimePassword => Self::IncorrectHexOneTimePassword, } }
pub fn add_tar<P, E>(path: P, error: E) -> Error where P: AsRef<Utf8Path>, E: std::error::Error, { Self::AddTar { path: path.as_ref().to_path_buf(), err: error.to_string(), } }
pub fn finish_tar<E>(error: E) -> Error where E: std::error::Error, { Self::TarFinish(error.to_string()) }
pub fn dependency_resolution_failed<T: PackageFetcher>( error: ResolutionError<'_, T>, root_package_name: EcoString, ) -> Error { match error { ResolutionError::NoSolution(derivation_tree) => Self::DependencyResolutionNoSolution { root_package_name, derivation_tree: Box::new(NeverEqual(derivation_tree)), },
ResolutionError::ErrorRetrievingDependencies { package, version, source, } => Self::DependencyResolutionError(format!( "An error occurred while trying to retrieve dependencies of {package}@{version}: {source}", )),
ResolutionError::ErrorChoosingVersion { package, source } => { Self::DependencyResolutionError(format!( "An error occurred while choosing the version of {package}: {source}", )) }
ResolutionError::ErrorInShouldCancel(err) => Self::DependencyResolutionError(format!( "Dependency resolution was cancelled. {err}" )), } }
pub fn expand_tar<E>(error: E) -> Error where E: std::error::Error, { Self::ExpandTar { error: error.to_string(), } }}
impl<T> From<Error> for Outcome<T, Error> { fn from(error: Error) -> Self { Outcome::TotalFailure(error) }}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub enum InvalidProjectNameReason { Format, FormatNotLowercase, GleamPrefix, ErlangReservedWord, ErlangStandardLibraryModule, GleamReservedWord, GleamReservedModule,}
pub fn format_invalid_project_name_error( name: &str, reason: &InvalidProjectNameReason, with_suggestion: &Option<String>,) -> String { let reason_message = match reason { InvalidProjectNameReason::ErlangReservedWord => "is a reserved word in Erlang.", InvalidProjectNameReason::ErlangStandardLibraryModule => { "is a standard library module in Erlang." } InvalidProjectNameReason::GleamReservedWord => "is a reserved word in Gleam.", InvalidProjectNameReason::GleamReservedModule => "is a reserved module name in Gleam.", InvalidProjectNameReason::FormatNotLowercase => { "does not have the correct format. Project names \may only contain lowercase letters." } InvalidProjectNameReason::Format => { "does not have the correct format. Project names \must start with a lowercase letter and may only contain lowercase letters, \numbers and underscores." } InvalidProjectNameReason::GleamPrefix => { "has the reserved prefix `gleam_`. \This prefix is intended for official Gleam packages only." } };
match with_suggestion { Some(suggested_name) => wrap_format!( "We were not able to create your project as `{}` {}
Would you like to name your project '{}' instead?", name, reason_message, suggested_name ), None => wrap_format!( "We were not able to create your project as `{}` {}
Please try again with a different project name.", name, reason_message ), }}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]pub enum StandardIoAction { Read, Write,}
impl StandardIoAction { fn text(&self) -> &'static str { match self { StandardIoAction::Read => "read from", StandardIoAction::Write => "write to", } }}
#[derive(Debug, PartialEq, Eq, Clone)]pub enum FileIoAction { Link(Utf8PathBuf), Open, // Path is `Option` here since there are some cases where we do not have // destination, like when writing ZIP archives Copy(Option<Utf8PathBuf>), Read, Parse, Delete, // Rename, Create, WriteTo, Canonicalise, UpdatePermissions, FindParent, ReadMetadata, Lock,}
impl FileIoAction { fn text(&self) -> &'static str { match self { FileIoAction::Link(..) => "link", FileIoAction::Open => "open", FileIoAction::Lock => "lock", FileIoAction::Copy(..) => "copy", FileIoAction::Read => "read", FileIoAction::Parse => "parse", FileIoAction::Delete => "delete", // FileIoAction::Rename => "rename", FileIoAction::Create => "create", FileIoAction::WriteTo => "write to", FileIoAction::FindParent => "find the parent of", FileIoAction::Canonicalise => "canonicalise", FileIoAction::UpdatePermissions => "update permissions of", FileIoAction::ReadMetadata => "read metadata of", } }
fn is_link(&self) -> bool { match self { FileIoAction::Link(..) => true,
FileIoAction::Open | FileIoAction::Copy(..) | FileIoAction::Read | FileIoAction::Lock | FileIoAction::Parse | FileIoAction::Delete | FileIoAction::Create | FileIoAction::WriteTo | FileIoAction::Canonicalise | FileIoAction::UpdatePermissions | FileIoAction::FindParent | FileIoAction::ReadMetadata => false, } }
/// Returns a destination path of action, if any fn destination(&self) -> Option<&Utf8PathBuf> { match self { FileIoAction::Link(destination) | FileIoAction::Copy(Some(destination)) => { Some(destination) }
FileIoAction::Open | FileIoAction::Read | FileIoAction::Lock | FileIoAction::Parse | FileIoAction::Delete | FileIoAction::Create | FileIoAction::WriteTo | FileIoAction::Canonicalise | FileIoAction::UpdatePermissions | FileIoAction::FindParent | FileIoAction::ReadMetadata | FileIoAction::Copy(None) => None, } }}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum FileKind { File, Directory,}
impl FileKind { fn text(&self) -> &'static str { match self { FileKind::File => "file", FileKind::Directory => "directory", } }}
// https://github.com/rust-lang/rust/blob/03994e498df79aa1f97f7bbcfd52d57c8e865049/compiler/rustc_span/src/edit_distance.rspub fn edit_distance(a: &str, b: &str, limit: usize) -> Option<usize> { let mut a = &a.chars().collect::<Vec<_>>()[..]; let mut b = &b.chars().collect::<Vec<_>>()[..];
if a.len() < b.len() { std::mem::swap(&mut a, &mut b); }
let min_dist = a.len() - b.len(); // If we know the limit will be exceeded, we can return early. if min_dist > limit { return None; }
// Strip common prefix. while !b.is_empty() && !a.is_empty() { let (b_first, b_rest) = b.split_last().expect("Failed to split 'b' slice"); let (a_first, a_rest) = a.split_last().expect("Failed to split 'a' slice");
if b_first == a_first { a = a_rest; b = b_rest; } else { break; } }
// If either string is empty, the distance is the length of the other. // We know that `b` is the shorter string, so we don't need to check `a`. if b.is_empty() { return Some(min_dist); }
let mut prev_prev = vec![usize::MAX; b.len() + 1]; let mut prev = (0..=b.len()).collect::<Vec<_>>(); let mut current = vec![0; b.len() + 1];
// row by row for i in 1..=a.len() { if let Some(element) = current.get_mut(0) { *element = i; } let a_idx = i - 1;
// column by column for j in 1..=b.len() { let b_idx = j - 1;
// There is no cost to substitute a character with itself. let substitution_cost = match (a.get(a_idx), b.get(b_idx)) { (Some(&a_char), Some(&b_char)) => { if a_char == b_char { 0 } else { 1 } } _ => panic!("Index out of bounds"), };
let insertion = current.get(j - 1).map_or(usize::MAX, |&x| x + 1);
if let Some(value) = current.get_mut(j) { *value = std::cmp::min( // deletion prev.get(j).map_or(usize::MAX, |&x| x + 1), std::cmp::min( // insertion insertion, // substitution prev.get(j - 1) .map_or(usize::MAX, |&x| x + substitution_cost), ), ); }
if (i > 1) && (j > 1) && let (Some(&a_val), Some(&b_val_prev), Some(&a_val_prev), Some(&b_val)) = ( a.get(a_idx), b.get(b_idx - 1), a.get(a_idx - 1), b.get(b_idx), ) && (a_val == b_val_prev) && (a_val_prev == b_val) { // transposition if let Some(curr) = current.get_mut(j) && let Some(&prev_prev_val) = prev_prev.get(j - 2) { *curr = std::cmp::min(*curr, prev_prev_val + 1); } } }
// Rotate the buffers, reusing the memory. [prev_prev, prev, current] = [prev, current, prev_prev]; }
// `prev` because we already rotated the buffers. let distance = match prev.get(b.len()) { Some(&d) => d, None => usize::MAX, }; (distance <= limit).then_some(distance)}
fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option<usize> { let n = a.chars().count(); let m = b.chars().count();
// Check one isn't less than half the length of the other. If this is true then there is a // big difference in length. let big_len_diff = (n * 2) < m || (m * 2) < n; let len_diff = m.abs_diff(n); let distance = edit_distance(a, b, limit + len_diff)?;
// This is the crux, subtracting length difference means exact substring matches will now be 0 let score = distance - len_diff;
// If the score is 0 but the words have different lengths then it's a substring match not a full // word match let score = if score == 0 && len_diff > 0 && !big_len_diff { 1 // Exact substring match, but not a total word match so return non-zero } else if !big_len_diff { // Not a big difference in length, discount cost of length difference score + len_diff.div_ceil(2) } else { // A big difference in length, add back the difference in length to the score score + len_diff };
(score <= limit).then_some(score)}
fn did_you_mean(name: &str, options: &[EcoString]) -> Option<String> { // If only one option is given, return that option. // This seems to solve the `unknown_variable_3` test. if options.len() == 1 { return options .first() .map(|option| format!("Did you mean `{option}`?")); }
// Check for case-insensitive matches. // This solves the comparison to small and single character terms, // such as the test on `type_vars_must_be_declared`. if let Some(exact_match) = options .iter() .find(|&option| option.eq_ignore_ascii_case(name)) { return Some(format!("Did you mean `{exact_match}`?")); }
// Calculate the threshold as one third of the name's length, with a minimum of 1. let threshold = std::cmp::max(name.chars().count() / 3, 1);
// Filter and sort options based on edit distance. options .iter() .filter(|&option| option != ast::CAPTURE_VARIABLE) .sorted() .filter_map(|option| { edit_distance_with_substrings(option, name, threshold) .map(|distance| (option, distance)) }) .min_by_key(|&(_, distance)| distance) .map(|(option, _)| format!("Did you mean `{option}`?"))}
fn to_ordinal(value: u32) -> String { match value % 10 { // All numbers starting with 1 end in `th` (11th, 12th, 13th, etc.) _ if value / 10 == 1 => format!("{value}th"), 1 => format!("{value}st"), 2 => format!("{value}nd"), 3 => format!("{value}rd"), _ => format!("{value}th"), }}
impl Error { pub fn pretty_string(&self) -> String { let mut nocolor = Buffer::no_color(); self.pretty(&mut nocolor); String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8") }
pub fn pretty(&self, buffer: &mut Buffer) { for diagnostic in self.to_diagnostics() { diagnostic.write(buffer); writeln!(buffer).expect("write new line after diagnostic"); } }
pub fn skipped_files_diagnostics(&self) -> HashMap<Utf8PathBuf, Diagnostic> { let Error::Type { skipped_modules, .. } = self else { return HashMap::new(); };
skipped_modules .iter() .map(|skipped_module| { let text = match &skipped_module.reason { SkipReason::DependencyHasError { .. } => { "The current module cannot be analysed because this \imported module has errors." .to_string() } SkipReason::DependencyWasSkipped { erroring_module, .. } => { format!( "The current module cannot be analysed because \this imported module depends on the `{erroring_module}` module, which has \errors." ) } };
let diagnostic = Diagnostic { title: "Imported module with errors".into(), text: wrap(&text), level: Level::Error, location: Some(Location { src: skipped_module.code.clone(), path: skipped_module.path.clone(), label: Label { text: None, span: skipped_module.location, }, extra_labels: vec![], }), hint: Some(format!( "fix the errors in the `{}` module first.", skipped_module.reason.erroring_module() )), };
(skipped_module.path.clone(), diagnostic) }) .collect() }
pub fn to_diagnostics(&self) -> Vec<Diagnostic> { match self { Error::HexSessionRevoked => vec![Diagnostic { title: "Hex session revoked".into(), text: "Your Hex session has been revoked or has expired.
You'll need to re-authenticate to continue using Hex." .into(), hint: Some("Run 'gleam hex authenticate' to log in again.".into()), level: Level::Error, location: None, }],
Error::TarPathOutsideOfProjectRoot { path } => vec![Diagnostic { title: "Cannot add path to tar archive".into(), text: wrap(&format!( "The path {path} is outside this Gleam project, \so we cannot safely add it to the archive for publishing. If we permitted this \then malicious actors could abuse this functionality to trick you into sharing \your private information.
Move the file into your Gleam project and try again." )), hint: None, level: Level::Error, location: None, }],
Error::IncorrectHexOneTimePassword => { let text = "That two-factor authentication code was rejected by Hex, please try again.
If you need to reconfigure your Hex two-factor security it can be donevia the Hex website: https://hex.pm/dashboard/security" .into();
vec![Diagnostic { title: "Incorrect two-factor authentication code".into(), text, level: Level::Error, location: None, hint: None, }] }
Error::HexPackageSquatting => { let text = "You appear to be attempting to reserve a name on Hex rather than publishing aworking package. This is against the Hex terms of service and can result inpackage deletion or account suspension." .into();
vec![Diagnostic { title: "Invalid Hex package".into(), text, level: Level::Error, location: None, hint: None, }] }
Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Default, } => { let text = "You appear to be attempting to publish a package with the default READMEgenerated by the `gleam new` command. That is meant as a placeholder and apublished package should have its own carefully written README." .into();
vec![Diagnostic { title: "Cannot publish with default README".into(), text, level: Level::Error, location: None, hint: Some( "Update your project's README to describe it before publishing".into(), ), }] }
Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Missing, } => { let text = "You appear to be attempting to publish a package with no README.All published packages should have one." .into();
vec![Diagnostic { title: "Cannot publish with no README".into(), text, level: Level::Error, location: None, hint: Some("Add a README to your project before publishing.".into()), }] }
Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Empty, } => { let text = "You appear to be attempting to publish a package with an empty README.All published packages should have a non empty README." .into();
vec![Diagnostic { title: "Cannot publish with empty README".into(), text, level: Level::Error, location: None, hint: Some( "Update your project's README to describe it before publishing".into(), ), }] }
Error::CannotPublishWithDefaultMain { package_name } => { let text = wrap_format!( "Packages with the default main function cannot be published
Remove or modify the main function that contains only: `io.println(\"Hello from {package_name}!\")`" );
vec![Diagnostic { title: "Cannot publish with default main function".into(), text, level: Level::Error, location: None, hint: None, }] }
Error::InvalidProjectName { name, reason } => { let text = format_invalid_project_name_error(name, reason, &None);
vec![Diagnostic { title: "Invalid project name".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::InvalidModuleName { module } => vec![Diagnostic { title: "Invalid module name".into(), text: format!( "`{module}` is not a valid module name.Module names can only contain lowercase letters, underscore, andforward slash and must not end with a slash." ), level: Level::Error, location: None, hint: None, }],
Error::ModuleDoesNotExist { module, suggestion } => { let hint = match suggestion { Some(suggestion) => format!("Did you mean `{suggestion}`?"), None => { // If the module ends with "_dev", it should be created in the "dev" // directory, if it ends with "_test", it should be created in the "test" // directory, and otherwise it should be created in "src" directory let directory = if module.ends_with("_dev") { "dev" } else if module.ends_with("_test") { "test" } else { "src" };
format!("Try creating the file `{directory}/{module}.gleam`.") } }; vec![Diagnostic { title: "Module does not exist".into(), text: format!("Module `{module}` was not found."), level: Level::Error, location: None, hint: Some(hint), }] }
Error::ModuleDoesNotHaveMainFunction { module, origin } => vec![Diagnostic { title: "Module does not have a main function".into(), text: wrap_format!( "`{module}` does not have a main function so the module can not be run." ), level: Level::Error, location: None, hint: Some(format!( "Add a public `main` function to `{}/{module}.gleam`.", origin.folder_name() )), }],
Error::MainFunctionIsPrivate { module } => vec![Diagnostic { title: "Module does not have a public main function".into(), text: wrap_format!( "`{module}` has a main function, but it is private, so it cannot be run." ), level: Level::Error, location: None, hint: Some(wrap_format!( "Make the `main` function in the `{module}` module public." )), }],
Error::MainFunctionDoesNotSupportTarget { module, target } => vec![Diagnostic { title: "Target not supported".into(), text: wrap_format!( "`{module}` has a main function, but it does not support the {target} \target, so it cannot be run.", target = target.as_presentable_str(), ), level: Level::Error, location: None, hint: None, }],
Error::MainFunctionHasWrongArity { module, arity } => vec![Diagnostic { title: "Main function has wrong arity".into(), text: wrap_format!( "`{module}.main` should take no arguments, but it takes {arity}." ), level: Level::Error, location: None, hint: Some( "Change the function signature of main to `pub fn main() -> Nil`.".into(), ), }],
Error::ProjectRootAlreadyExist { path } => vec![Diagnostic { title: "Project folder already exists".into(), text: format!("Project folder root:\n\n {path}"), level: Level::Error, hint: None, location: None, }],
Error::OutputFilesAlreadyExist { file_names } => vec![Diagnostic { title: format!( "{} already exist{} in target directory", if file_names.len() == 1 { "File" } else { "Files" }, if file_names.len() == 1 { "" } else { "s" } ), text: format!( "{}If you want to overwrite these files, delete them and run the command again.", file_names .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::RemovedPackagesNotExist { packages } => vec![Diagnostic { title: "Package not found".into(), text: format!( "These packages are not dependencies of your package so they could notbe removed.
{}", packages .iter() .map(|p| format!(" - {}", p.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::PackagesToUpdateNotExist { packages } => vec![Diagnostic { title: "Packages to update not found".into(), text: format!( "These packages are not dependencies of your package so they could notbe updated.
{}", packages .iter() .map(|p| format!(" - {}", p.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::CannotPublishTodo { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules contain todo expressions and cannot be published:
{}
Please remove them and try again.", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::CannotPublishEcho { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules contain echo expressions and cannot be published:
{}
`echo` is only meant for debug printing, please remove them and try again.", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::CannotPublishWrongVersion { minimum_required_version, wrongfully_allowed_version, } => { let minimum_required_version = minimum_required_version.to_semver_string(); let wrongfully_allowed_version = wrongfully_allowed_version.to_semver_string();
vec![Diagnostic { title: "Cannot publish package with wrong Gleam version range".into(), text: wrap(&format!( "Your package uses features that require at least v{minimum_required_version}.But the Gleam version range specified in your `gleam.toml` would allow this \code to run on an earlier version like v{wrongfully_allowed_version}, \resulting in compilation errors!" )), level: Level::Error, hint: Some(format!( "Remove the version constraint from your `gleam.toml` or update it to be:
gleam = \">= {minimum_required_version}\"" )), location: None, }] }
Error::CannotPublishLeakedInternalType { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules leak internal types in their public API and cannot be published:
{}
Please make sure internal types do not appear in public functions and try again.", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::CannotPublishEmptyModules { unfinished } => vec![Diagnostic { title: "Cannot publish empty modules".into(), text: wrap_format!( "These modules contain no public definitions and cannot be published:
{}
Please add public functions, types, or constants to these modules, or remove them and try again.", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }],
Error::UnableToFindProjectRoot { path } => { let text = wrap_format!( "We were unable to find gleam.toml.
We searched in {path} and all parent directories." ); vec![Diagnostic { title: "Project not found".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::VersionDoesNotMatch { toml_ver, app_ver } => { let text = format!( "The version in gleam.toml \"{toml_ver}\" does not match the version inyour app.src file \"{app_ver}\"." ); vec![Diagnostic { title: "Version does not match".into(), hint: None, text, level: Level::Error, location: None, }] }
Error::ShellProgramNotFound { program, os } => { let mut text = format!("The program `{program}` was not found. Is it installed?");
match os { OS::MacOS => { fn brew_install(name: &str, pkg: &str) -> String { format!("\n\nYou can install {name} via homebrew: brew install {pkg}",) } match program.as_str() { "erl" | "erlc" | "escript" => { text.push_str(&brew_install("Erlang", "erlang")); } "rebar3" => text.push_str(&brew_install("Rebar3", "rebar3")), "deno" => text.push_str(&brew_install("Deno", "deno")), "elixir" => text.push_str(&brew_install("Elixir", "elixir")), "node" => text.push_str(&brew_install("Node.js", "node")), "bun" => text.push_str(&brew_install("Bun", "oven-sh/bun/bun")), "git" => text.push_str(&brew_install("Git", "git")), _ => (), } } OS::Linux(distro) => { fn apt_install(name: &str, pkg: &str) -> String { format!("\n\nYou can install {name} via apt: sudo apt install {pkg}") } match distro { Distro::Ubuntu | Distro::Debian => match program.as_str() { "elixir" => text.push_str(&apt_install("Elixir", "elixir")), "git" => text.push_str(&apt_install("Git", "git")), _ => (), }, Distro::Other => (), } } OS::Windows | OS::Other => (), }
text.push('\n');
match program.as_str() { "erl" | "erlc" | "escript" => text.push_str( "Documentation for installing Erlang can be viewed here:https://gleam.run/getting-started/installing/", ), "rebar3" => text.push_str( "Documentation for installing Rebar3 can be viewed here:https://rebar3.org/docs/getting-started/", ), "deno" => text.push_str( "Documentation for installing Deno can be viewed here:https://docs.deno.com/runtime/getting_started/installation/", ), "elixir" => text.push_str( "Documentation for installing Elixir can be viewed here:https://elixir-lang.org/install.html", ), "node" => text.push_str( "Documentation for installing Node.js via package manager can be viewed here:https://nodejs.org/en/download/package-manager/all/", ), "bun" => text.push_str( "Documentation for installing Bun can be viewed here:https://bun.sh/docs/installation/", ), "git" => text.push_str( "Documentation for installing Git can be viewed here:https://git-scm.com/book/en/v2/Getting-Started-Installing-Git", ), _ => (), }
vec![Diagnostic { title: "Program not found".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ShellCommand { program: command, reason: ShellCommandFailureReason::Unknown, } => { let text = format!("There was a problem when running the shell command `{command}`."); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ShellCommand { program: command, reason: ShellCommandFailureReason::IoError(err), } => { let text = format!( "There was a problem when running the shell command `{}`.
The error from the shell command library was:
{}", command, std_io_error_kind_text(err) ); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ShellCommand { program: command, reason: ShellCommandFailureReason::ShellCommandError(err), } => { let text = format!( "There was a problem when running the shell command `{command}`.
The error from the shell command was:
{err}" ); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::Gzip(detail) => { let text = format!( "There was a problem when applying gzip compression.
This was error from the gzip library:
{detail}" ); vec![Diagnostic { title: "Gzip compression failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::AddTar { path, err } => { let text = wrap_format!( "There was a problem when attempting to add the file {path} \to a tar archive.
This was error from the tar library:
{err}" ); vec![Diagnostic { title: "Failure creating tar archive".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ExpandTar { error } => { let text = format!( "There was a problem when attempting to expand a to a tar archive.
This was error from the tar library:
{error}" ); vec![Diagnostic { title: "Failure opening tar archive".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::TarFinish(detail) => { let text = format!( "There was a problem when creating a tar archive.
This was error from the tar library:
{detail}" ); vec![Diagnostic { title: "Failure creating tar archive".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ZipAdd { path, error: err } => { let text = wrap_format!( "There was a problem when attempting to add the file {path} \to a zip archive.
This was error from the zip library:
{err}" ); vec![Diagnostic { title: "Failure creating zip archive".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ZipFinish(detail) => { let text = format!( "There was a problem when creating a zip archive.
This was error from the zip library:
{detail}" ); vec![Diagnostic { title: "Failure creating zip archive".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::Hex(detail) => { let text = format!( "There was a problem when using the Hex API.
This was error from the Hex client library:
{detail}" ); vec![Diagnostic { title: "Hex API failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::DuplicateModule { module, first, second, } => { // If the conflicting modules come from the same package just // showing the same package name twice is not all that useful. // So we will show the path to each one! let should_show_path = first.package_name == second.package_name; let format_origin = |origin: &DefinedModuleOrigin| { if should_show_path { format!("at {}", origin.path) } else { format!("by the package {}", origin.package_name) } };
let text = format!( "The module `{module}` is defined multiple times.
It is first defined {}It is defined a second time {}", format_origin(first), format_origin(second) );
vec![Diagnostic { title: "Duplicate module".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::ClashingGleamModuleAndNativeFileName { module, gleam_file, native_file, } => { let text = format!( "The Gleam module `{module}` is clashing with a native filewith the same name:
Gleam module: {gleam_file} Native file: {native_file}
This is a problem because the Gleam module would be compiled to a file with thesame name and extension, unintentionally overwriting the native file." );
vec![Diagnostic { title: "Gleam module clashes with native file".into(), text, hint: Some( "Consider renaming one of the files, such as by \adding an `_ffi` suffix to the native file's name, and trying again." .into(), ), level: Level::Error, location: None, }] }
Error::DuplicateSourceFile { file } => vec![Diagnostic { title: "Duplicate Source file".into(), text: format!("The file `{file}` is defined multiple times."), hint: None, level: Level::Error, location: None, }],
Error::DuplicateNativeErlangModule { module, first, second, } => { let text = format!( "The native Erlang module `{module}` is defined multiple times.
First: {first}Second: {second}
Erlang modules must have unique names regardless of the subfolders where their`.erl` files are located." );
vec![Diagnostic { title: "Duplicate native Erlang module".into(), text, hint: Some("Rename one of the native Erlang modules and try again.".into()), level: Level::Error, location: None, }] }
Error::CouldNotCreateTempFile { error } => vec![Diagnostic { title: "File IO failure".into(), text: wrap_format!("Could not create temporary file:\n\n\t{error}"), level: Level::Error, location: None, hint: None, }],
Error::FileIo { kind, action, path, err, } => { let err = match err { Some(e) => { format!("\nThe error message from the file IO library was:\n\n {e}\n") } None => "".into(), }; let destination = if let Some(destination) = action.destination() { format!( "\n\nTo:
{destination}" ) } else { "".into() }; let mut text = format!( "An error occurred while trying to {} this {}:
{}{}{}", action.text(), kind.text(), path, destination, err, ); if cfg!(target_family = "windows") && action.is_link() { text.push_str("
Windows does not support symbolic links without developer modeor admin privileges. Please enable developer mode and try again.
https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode"); } vec![Diagnostic { title: "File IO failure".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::FailedToEncryptLocalHexApiKey { detail } => { let text = wrap_format!( "A problem was encountered \encrypting the local Hex API key with the given password.The error from the encryption library was:
{detail}" ); vec![Diagnostic { title: "Failed to encrypt data".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::FailedToDecryptLocalHexApiKey => { let text = wrap_format!( "Unable to decrypt the local Hex API key with the given password.Did you make a typo? Please try again." ); vec![Diagnostic { title: "Failed to decrypt local Hex API key".into(), text, hint: Some( "If you have forgotten your local password you can create a new onewith `gleam hex authenticate`." .into(), ), level: Level::Error, location: None, }] }
Error::NonUtf8Path { path } => { let text = format!( "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.", path.to_string_lossy() ); vec![Diagnostic { title: "Non UTF-8 Path Encountered".into(), text, level: Level::Error, location: None, hint: None, }] }
Error::GitInitialization { error } => { let text = format!( "An error occurred while trying make a git repository for this project:
{error}" ); vec![Diagnostic { title: "Failed to initialize git repository".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::Type { skipped_modules: _, failed_modules, } => failed_modules .values() .sorted_by_key(|failed_module| &failed_module.path) .flat_map(failed_module_diagnostics) .collect_vec(),
Error::Parse { path, src, error } => { let location = if error.error == ParseErrorType::UnexpectedEof { SrcSpan { start: (src.len() - 1) as u32, end: (src.len() - 1) as u32, } } else { error.location };
let title = String::from("Syntax error"); let ParseErrorDetails { text, label_text, extra_labels, hint, } = error.error.details(); vec![Diagnostic { title, text, level: Level::Error, location: Some(Location { src: src.clone(), path: path.clone(), label: Label { text: Some(label_text.into()), span: location, }, extra_labels, }), hint, }] }
Error::ImportCycle { modules } => { let first_location = &modules.first().1; let rest_locations = modules .iter() .skip(1) .map(|(_, l)| ExtraLabel { label: Label { text: Some("Imported here".into()), span: l.location, }, src_info: Some((l.src.clone(), l.path.clone())), }) .collect_vec(); let mut text = "The import statements for these modules form a cycle:" .into(); let mod_names = modules.iter().map(|m| m.0.clone()).collect_vec(); write_cycle(&mut text, &mod_names); text.push_str(&wrap( "Gleam doesn't support dependency cycles like these, \please break the cycle to continue.", )); vec![Diagnostic { title: "Import cycle".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: first_location.location, }, path: first_location.path.clone(), src: first_location.src.clone(), extra_labels: rest_locations, }), }] }
Error::PackageCycle { packages } => { let mut text = "The dependencies for these packages form a cycle:" .into(); write_cycle(&mut text, packages); text.push_str(&wrap( "Gleam doesn't support dependency cycles like these, \please break the cycle to continue.", )); vec![Diagnostic { title: "Dependency cycle".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::UnknownImport { import, details } => { let UnknownImportDetails { module, location, path, src, modules, } = details.as_ref(); let text = wrap(&format!( "The module `{module}` is trying to import the module \`{import}`, but it cannot be found." )); vec![Diagnostic { title: "Unknown import".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: did_you_mean(import, modules), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }] }
Error::StandardIo { action, err } => { let err = match err { Some(e) => format!( "\nThe error message from the stdio library was:\n\n {}\n", std_io_error_kind_text(e) ), None => "".into(), }; vec![Diagnostic { title: "Standard IO failure".into(), text: format!( "An error occurred while trying to {}:
{}", action.text(), err, ), hint: None, location: None, level: Level::Error, }] }
Error::Format { problem_files } => { let files: Vec<_> = problem_files .iter() .map(|formatted| formatted.source.as_str()) .map(|p| format!(" - {p}")) .sorted() .collect(); let mut text = files.iter().join("\n"); text.push('\n'); vec![Diagnostic { title: "These files have not been formatted".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::ForbiddenWarnings { count } => { let word_warning = match count { 1 => "warning", _ => "warnings", }; let text = "Your project was compiled with the `--warnings-as-errors` flag.Fix the warnings and try again." .into(); vec![Diagnostic { title: format!("{count} {word_warning} generated."), text, hint: None, location: None, level: Level::Error, }] }
Error::DownloadPackageError { package_name, package_version, error, } => { let text = format!( "A problem was encountered when downloading `{package_name}` {package_version}.The error from the package manager client was:
{error}" ); vec![Diagnostic { title: "Failed to download package".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::Http(error) => { let text = format!( "A HTTP request failed.The error from the HTTP client was:
{error}" ); vec![Diagnostic { title: "HTTP error".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::InvalidVersionFormat { input, error } => { let text = format!( "I was unable to parse the version \"{input}\".The error from the parser was:
{error}" ); vec![Diagnostic { title: "Invalid version format".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::IncompatibleLockedVersion { error } => { let text = format!( "There is an incompatiblity between a version specified inmanifest.toml and a version range specified in gleam.toml:
{error}" ); vec![Diagnostic { title: "Incompatible locked version".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::DependencyCanonicalizationFailed(package) => { let text = format!("Local package `{package}` has no canonical path");
vec![Diagnostic { title: "Failed to create canonical path".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::DependencyResolutionError(error) => vec![Diagnostic { title: "Dependency resolution failed".into(), text: wrap(error), hint: None, location: None, level: Level::Error, }],
Error::DependencyResolutionNoSolution { root_package_name, derivation_tree, } => { let text = wrap( &DerivationTreePrinter::new( root_package_name.clone(), derivation_tree.0.clone(), ) .print(), ); vec![Diagnostic { title: "Dependency resolution failed".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::WrongDependencyProvided { path, expected, found, } => { let text = format!( "Expected package `{expected}` at path `{path}` but found `{found}` instead.", );
vec![Diagnostic { title: "Wrong dependency provided".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::ProvidedDependencyConflict { package, source_1, source_2, } => { let text = format!( "The package `{package}` is provided as both `{source_1}` and `{source_2}`.", );
vec![Diagnostic { title: "Conflicting provided dependencies".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::GitDependencyPathNotFound { package, path, repo, } => { let text = format!( "The path `{path}` does not exist in the git repository `{repo}` \for package `{package}`." );
vec![Diagnostic { title: "Git dependency path not found".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::DuplicateDependency(name) => { let text = format!( "The package `{name}` is specified in both the dependencies anddev_dependencies sections of the gleam.toml file." ); vec![Diagnostic { title: "Dependency duplicated".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::MissingHexPublishFields { description_missing, licence_missing, } => { let mut text = "Licence information and package description are required to publish apackage to Hex.\n" .to_string(); text.push_str(if *description_missing && *licence_missing { r#"Add the licences and description fields to your gleam.toml file.
description = ""licences = ["Apache-2.0"]"# } else if *description_missing { r#"Add the description field to your gleam.toml file.
description = """# } else { r#"Add the licences field to your gleam.toml file.
licences = ["Apache-2.0"]"# }); vec![Diagnostic { title: "Missing required package fields".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::PublishNonHexDependencies { package } => vec![Diagnostic { title: "Unpublished dependencies".into(), text: wrap_format!( "The package cannot be published to Hex \because dependency `{package}` is not a Hex dependency.", ), hint: None, location: None, level: Level::Error, }],
Error::UnsupportedBuildTool { package, build_tools, } => { let text = wrap_format!( "The package `{}` cannot be built as it does not use \a build tool supported by Gleam. It uses {:?}.
If you would like us to support this package please let us know by opening an \issue in our tracker: https://github.com/gleam-lang/gleam/issues", package, build_tools ); vec![Diagnostic { title: "Unsupported build tool".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::FailedToOpenDocs { path, error } => { let error = format!("\nThe error message from the library was:\n\n {error}\n"); let text = format!( "An error occurred while trying to open the docs:
{path}{error}", ); vec![Diagnostic { title: "Failed to open docs".into(), text, hint: None, level: Level::Error, location: None, }] }
Error::IncompatibleCompilerVersion { package, required_version, gleam_version, } => { let text = format!( "The package `{package}` requires a Gleam version \satisfying {required_version} but you are using v{gleam_version}.", ); vec![Diagnostic { title: "Incompatible Gleam version".into(), text, hint: None, location: None, level: Level::Error, }] }
Error::InvalidRuntime { target, invalid_runtime, } => { let text = format!( "Invalid runtime for {target} target: {invalid_runtime}", target = target.as_presentable_str(), invalid_runtime = invalid_runtime.as_presentable_str(), );
let hint = match target { Target::JavaScript => { Some("available runtimes for JavaScript are: node, deno.".into()) } Target::Erlang => Some( "You can not set a runtime for Erlang. Did you mean to target JavaScript?" .into(), ), Target::Wasm => Some( "You can not set a runtime for Wasm. Did you mean to target JavaScript?" .into(), ), };
vec![Diagnostic { title: format!( "Invalid runtime for {target}", target = target.as_presentable_str(), ), text, hint, location: None, level: Level::Error, }] }
Error::JavaScriptPreludeRequired => vec![Diagnostic { title: "JavaScript prelude required".into(), text: "The --javascript-prelude flag must be given when compiling to JavaScript." .into(), level: Level::Error, location: None, hint: None, }], Error::CorruptManifest => vec![Diagnostic { title: "Corrupt manifest.toml".into(), text: "The `manifest.toml` file is corrupt.".into(), level: Level::Error, location: None, hint: Some("Please run `gleam update` to fix it.".into()), }],
Error::GleamModuleWouldOverwriteStandardErlangModule { name, path } => { vec![Diagnostic { title: "Erlang module name collision".into(), text: wrap_format!( "The module `{path}` compiles to an Erlang module \named `{name}`.
By default Erlang includes a module with the same name so if we were \to compile and load your module it would overwrite the Erlang one, potentially \causing confusing errors and crashes." ), level: Level::Error, location: None, hint: Some("Rename this module and try again.".into()), }] }
Error::HexPublishReplaceRequired { version } => vec![Diagnostic { title: "Version already published".into(), text: wrap_format!( "Version v{version} has already been published.This release has been recently published so you can replace it \or you can publish it using a different version number" ), level: Level::Error, location: None, hint: Some( "Please add the --replace flag if you want to replace the release.".into(), ), }], Error::HexPublishAccessDenied { name, version } => vec![Diagnostic { title: "Access denied".to_string(), text: wrap_format!( "You are not one of the maintainers of the {name} package, so \you cannot publish a new {version} version. Are you logged into the correct account?
If you are trying to publish a new package then you will need to pick another, \as this one is already in use." ), level: Level::Error, location: None, hint: None, }],
Error::CannotAddSelfAsDependency { name } => vec![Diagnostic { title: "Dependency cycle".into(), text: wrap_format!( "A package cannot depend on itself, so you cannot \add `gleam add {name}` in this project." ), level: Level::Error, location: None, hint: None, }], } }}
fn failed_module_diagnostics(failed_module: &FailedModule) -> impl Iterator<Item = Diagnostic> { use crate::type_::Error as TypeError; let FailedModule { path, src, errors, names, } = failed_module;
errors.iter().map(|error| match error { TypeError::LiteralFloatOutOfRange { location, .. } => Diagnostic { title: "Float outside of valid range".into(), text: wrap( "This float value is too large to be represented by \a floating point type: float values must be in the range -1.7976931348623157e308 \- 1.7976931348623157e308.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::SrcImportingTest, } => { let text = wrap_format!( "The application module `{importing_module}` \is importing the test module `{imported_module}`.
Test modules are not included in production builds so application \modules cannot import them. Perhaps move the `{imported_module}` \module to the src directory.", );
Diagnostic { title: "App importing test module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::SrcImportingDev, } => { let text = wrap_format!( "The application module `{importing_module}` \is importing the development module `{imported_module}`.
Development modules are not included in production builds so application \modules cannot import them. Perhaps move the `{imported_module}` \module to the src directory.", );
Diagnostic { title: "App importing dev module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::DevImportingTest, } => { let text = wrap_format!( "The development module `{importing_module}` \is importing the test module `{imported_module}`.
Test modules should only contain test-related code, and not general development \code. Perhaps move the `{imported_module}` module to the dev directory.", );
Diagnostic { title: "Dev importing test module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnknownLabels { unknown, valid, supplied, } => { let other_labels: Vec<_> = valid .iter() .filter(|label| !supplied.contains(label)) .cloned() .collect();
let title = if unknown.len() > 1 { "Unknown labels" } else { "Unknown label" } .into();
let mut labels = unknown.iter().map(|(label, location)| { let text = did_you_mean(label, &other_labels).unwrap_or_else(|| "Unexpected label".into()); Label { text: Some(text), span: *location, } }); let label = labels.next().expect("Unknown labels first label"); let extra_labels = labels .map(|label| ExtraLabel { src_info: None, label, }) .collect(); let text = if valid.is_empty() { "This constructor does not accept any labelled arguments.".into() } else if other_labels.is_empty() { "You have already supplied all the labelled arguments that thisconstructor accepts." .into() } else { let mut label_text = String::from("It accepts these labels:\n"); for label in other_labels.iter().sorted() { label_text.push_str("\n "); label_text.push_str(label); } label_text }; Diagnostic { title, text, hint: None, level: Level::Error, location: Some(Location { label, path: path.clone(), src: src.clone(), extra_labels, }), } }
TypeError::UnexpectedLabelledArg { location, label, kind, } => { let kind = match kind { UnexpectedLabelledArgKind::FunctionParameter => "function", UnexpectedLabelledArgKind::RecordConstructorArgument => "record constructor", }; let text = format!( "This argument has been given a label but the {kind} doesnot expect any. Please remove the label `{label}`." ); Diagnostic { title: "Unexpected labelled argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::PositionalArgumentAfterLabelled { location } => { let text = wrap( "This unlabeled argument has been \supplied after a labelled argument.Once a labelled argument has been supplied all following arguments mustalso be labelled.", );
Diagnostic { title: "Unexpected positional argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::DuplicateImport { location, previous_location, name, } => { let text = format!( "`{name}` has been imported multiple times.Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate import".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Reimported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First imported here".into()), span: *previous_location, }, }], }), } }
TypeError::DuplicateName { location_a, location_b, name, .. } => { let (first_location, second_location) = if location_a.start < location_b.start { (location_a, location_b) } else { (location_b, location_a) }; let text = format!( "`{name}` has been defined multiple times.Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate definition".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Redefined here".into()), span: *second_location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First defined here".into()), span: *first_location, }, }], }), } }
TypeError::DuplicateTypeName { name, location, previous_location, .. } => { let text = format!( "The type `{name}` has been defined multiple times.Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate type definition".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Redefined here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First defined here".into()), span: *previous_location, }, }], }), } }
TypeError::DuplicateField { location, label } => { let text = format!("The label `{label}` has already been defined. Rename this label."); Diagnostic { title: "Duplicate label".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::DuplicateArgument { location, label } => { let text = format!("The labelled argument `{label}` has already been supplied."); Diagnostic { title: "Duplicate argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::RecursiveType { location } => { let text = wrap( "I don't know how to work out what type this \value has. It seems to be defined in terms of itself.", ); Diagnostic { title: "Recursive type".into(), text, hint: Some("Add some type annotations and try again.".into()), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::NotFn { location, type_ } => { let mut printer = Printer::new(names); let text = format!( "This value is being called as a function but its type is:\n\n {}", printer.print_type(type_) ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnknownRecordField { usage, location, type_, label, fields, unknown_field: variants, } => { let mut printer = Printer::new(names);
// Give a hint about what type this value has. let mut text = format!( "The value being accessed has this type:\n\n {}\n", printer.print_type(type_) );
// Give a hint about what record fields this value has, if any. if fields.is_empty() { if variants == &UnknownField::NoFields { text.push_str("\nIt does not have any fields."); } else { text.push_str( "\nIt does not have fields that are common \across all variants.", ); } } else { text.push_str("\nIt has these accessible fields:\n"); } for field in fields.iter().sorted() { text.push_str("\n ."); text.push_str(field); }
match variants { UnknownField::AppearsInAVariant => { let msg = wrap( "Note: The field you are trying to access is \not defined consistently across all variants of this custom type. To fix this, \ensure that all variants include the field with the same name, position, and \type.", ); text.push_str("\n\n"); text.push_str(&msg); } UnknownField::AppearsInAnImpossibleVariant => { let msg = wrap( "Note: The field exists in this custom type \but is not defined for the current variant. Ensure that you are accessing the \field on a variant where it is valid.", ); text.push_str("\n\n"); text.push_str(&msg); } UnknownField::TrulyUnknown => (), UnknownField::NoFields => (), }
// Give a hint about Gleam not having OOP methods if it // looks like they might be trying to call one. match usage { FieldAccessUsage::MethodCall => { let msg = wrap( "Gleam is not object oriented, so if you are trying \to call a method on this value you may want to use the function syntax instead.", ); text.push_str("\n\n"); text.push_str(&msg); text.push_str("\n\n "); text.push_str(label); text.push_str("(value)"); } FieldAccessUsage::Other | FieldAccessUsage::RecordUpdate => (), }
let label = did_you_mean(label, fields).unwrap_or_else(|| "This field does not exist".into()); Diagnostic { title: "Unknown record field".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::CouldNotUnify { location, expected, given, situation: Some(UnifyErrorSituation::Operator(op)), } => { let mut printer = Printer::new(names); let text = format!( "The {op} operator expects arguments of this type:
{expected}
But this argument has this type:
{given}", op = op.name(), expected = printer.print_type(expected), given = printer.print_type(given), ); Diagnostic { title: "Type mismatch".into(), text, hint: hint_alternative_operator(op, given), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::CouldNotUnify { location, expected, given, situation: Some(UnifyErrorSituation::PipeTypeMismatch), } => { // Remap the pipe function type into just the type expected by the pipe. let expected = expected .fn_types() .and_then(|(arguments, _)| arguments.first().cloned());
// Remap the argument as well, if it's a function. let given = given .fn_types() .and_then(|(arguments, _)| arguments.first().cloned()) .unwrap_or_else(|| given.clone());
let mut printer = Printer::new(names); let text = format!( "The argument is:
{given}
But function expects:
{expected}", expected = expected .map(|v| printer.print_type(&v)) .unwrap_or_else(|| " No arguments".into()), given = printer.print_type(&given) );
Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This function does not accept the piped type".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::CouldNotUnify { location, expected, given, situation, } => { let mut printer = Printer::new(names); let mut text = if let Some(description) = situation.as_ref().and_then(|s| s.description()) { let mut text = description.to_string(); text.push('\n'); text.push('\n'); text } else { "".into() }; text.push_str("Expected type:\n\n "); text.push_str(&printer.print_type(expected)); text.push_str("\n\nFound type:\n\n "); text.push_str(&printer.print_type(given));
let (main_message_location, main_message_text, extra_labels) = match situation { // When the mismatch error comes from a case clause we want to highlight the // entire branch (pattern included) when reporting the error; in addition, // if the error could be resolved just by wrapping the value in an `Ok` // or `Error` we want to add an additional label with this hint below the // offending value. Some(UnifyErrorSituation::CaseClauseMismatch { clause_location }) => { (clause_location, None, vec![]) } // In all other cases we just highlight the offending expression, optionally // adding the wrapping hint if it makes sense. Some(_) | None => (location, hint_wrap_value_in_result(expected, given), vec![]), };
Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: main_message_text, span: *main_message_location, }, path: path.clone(), src: src.clone(), extra_labels, }), } }
TypeError::IncorrectTypeArity { location, expected, given: given_number, name, } => { let expected = match expected { 0 => "no type arguments".into(), 1 => "1 type argument".into(), _ => format!("{expected} type arguments"), }; let given = match given_number { 0 => "none", _ => &format!("{given_number}"), }; let text = wrap_format!( "`{name}` requires {expected} \but {given} where provided." ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("Expected {expected}, got {given_number}")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::TypeUsedAsAConstructor { location, name } => { let text = wrap_format!( "`{name}` is a type with no parameters, but here it's \being used as a type constructor." );
Diagnostic { title: "Type used as a type constructor".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("You can remove this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::IncorrectArity { labels, location, context, expected, given, } => { let text = if labels.is_empty() { "".into() } else { let subject = match context { IncorrectArityContext::Pattern => "pattern", IncorrectArityContext::Function => "call", }; let labels = labels .iter() .map(|p| format!(" - {p}")) .sorted() .join("\n"); format!( "This {subject} accepts these additional labelled \ arguments:\n\n{labels}", ) }; let expected = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), }; let label = format!("Expected {expected}, got {given}"); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnnecessarySpreadOperator { location, arity } => { let text = wrap_format!( "This record has {arity} fields and you have already \assigned variables to all of them." ); Diagnostic { title: "Unnecessary spread operator".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnsafeRecordUpdate { location, reason } => match reason { UnsafeRecordUpdateReason::UnknownVariant { constructed_variant, } => { let text = wrap_format!( "This value cannot be used to build an updated \`{constructed_variant}` as it could be some other variant.
Consider pattern matching on it with a case expression and then \constructing a new record with its values." );
Diagnostic { title: "Unsafe record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!( "I'm not sure this is always a `{constructed_variant}`" )), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } UnsafeRecordUpdateReason::WrongVariant { constructed_variant, spread_variant, } => { let text = wrap_format!( "This value is a `{spread_variant}` so \it cannot be used to build a `{constructed_variant}`, even if they share some fields.
Note: If you want to change one variant of a type into another, you should \specify all fields explicitly instead of using the record update syntax." );
Diagnostic { title: "Incorrect record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("This is a `{spread_variant}`")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } UnsafeRecordUpdateReason::IncompatibleFieldTypes { expected_field_type, record_field_type, record_variant, field, .. } => { let mut printer = Printer::new(names); let expected_field_type = printer.print_type(expected_field_type); let record_field_type = printer.print_type(record_field_type); let record_variant = printer.print_type(record_variant); let text = match field { RecordField::Labelled(label) => wrap_format!( "The `{label}` field \of this value is a `{record_field_type}`, but the arguments given to the record \update indicate that it should be a `{expected_field_type}`.
Note: If the same type variable is used for multiple fields, all those fields \need to be updated at the same time if their type changes." ), RecordField::Unlabelled(index) => wrap_format!( "The {} field \of this value is a `{record_field_type}`, but the arguments given to the record \update indicate that it should be a `{expected_field_type}`.
Note: Unlabelled fields cannot be updated in a record update, so either add \a label or use a record constructor.", to_ordinal(*index + 1), ), };
Diagnostic { title: "Incomplete record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("This is a `{record_variant}`")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } },
TypeError::QualifiedTypeMissingName { location } => Diagnostic { title: "Invalid type".into(), text: "".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a valid type".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::UnknownType { location, name, hint, .. } => { let label_text = match hint { UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types), UnknownTypeHint::ValueInScopeWithSameName => None, };
let mut text = wrap_format!("The type `{name}` is not defined or imported in this module.");
match hint { UnknownTypeHint::ValueInScopeWithSameName => { let hint = wrap_format!( "There is a value in scope with the name `{name}`, \but no type in scope with that name." ); text.push('\n'); text.push_str(hint.as_str()); } UnknownTypeHint::AlternativeTypes(_) => {} }
Diagnostic { title: "Unknown type".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: label_text, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnknownVariable { location, variables, discarded_location, name, type_with_name_in_scope, possible_modules, } => { let title = String::from("Unknown variable");
if let Some(ignored_location) = discarded_location { let location = Location { label: Label { text: Some("So this is not in scope".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("This value is discarded".into()), span: *ignored_location, }, }], }; Diagnostic { title, text: "".into(), hint: Some(wrap_format!( "Change `_{name}` to `{name}` or reference another variable", )), level: Level::Error, location: Some(location), } } else { let text = if *type_with_name_in_scope { wrap_format!("`{name}` is a type, it cannot be used as a value.") } else { let mut text = if name.starts_with(char::is_uppercase) { wrap_format!( "The custom type variant constructor `{name}` is not in scope here." ) } else { wrap_format!("The name `{name}` is not in scope here.") };
// If there are some suggestions about public values in imported // modules put a "did you mean" text after the main message if !possible_modules.is_empty() { let message = if possible_modules.len() == 1 { "\nDid you mean:\n\n" } else { "\nDid you mean one of these:\n\n" }; text.push_str(message); for module_name in possible_modules { text.push_str(" - "); text.push_str(module_name); text.push('.'); text.push_str(name); text.push('\n'); } }
text };
Diagnostic { title, text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: did_you_mean(name, variables), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } }
TypeError::PrivateTypeLeak { location, leaked } => { let mut printer = Printer::new(names);
// TODO: be more precise. // - is being returned by this public function // - is taken as an argument by this public function // - is taken as an argument by this public enum constructor // etc let text = wrap_format!( "The following type is private, but is \being used by this public export.
{}
Private types can only be used within the module that defines them.", printer.print_type(leaked), ); Diagnostic { title: "Private type used in public interface".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnknownModule { location, name, suggestions, } => Diagnostic { title: "Unknown module".into(), text: format!("No module has been found with the name `{name}`."), hint: suggestions .first() .map(|suggestion| suggestion.suggestion(name)), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::UnknownModuleType { location, name, module_name, type_constructors, value_with_same_name: imported_type_as_value, } => { let text = if *imported_type_as_value { format!("`{name}` is only a value, it cannot be imported as a type.") } else { format!("The module `{module_name}` does not have a `{name}` type.") }; Diagnostic { title: "Unknown module type".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: if *imported_type_as_value { Some(format!("Did you mean `{name}`?")) } else { did_you_mean(name, type_constructors) }, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UnknownModuleValue { location, name, module_name, value_constructors, type_with_same_name: imported_value_as_type, context, } => { let text = if *imported_value_as_type { match context { ModuleValueUsageContext::UnqualifiedImport => { wrap_format!("`{name}` is only a type, it cannot be imported as a value.") } ModuleValueUsageContext::ModuleAccess => wrap_format!( "{module_name}.{name} is a type constructor, \it cannot be used as a value" ), } } else { wrap_format!("The module `{module_name}` does not have a `{name}` value.") }; Diagnostic { title: "Unknown module value".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: if *imported_value_as_type && matches!(context, ModuleValueUsageContext::UnqualifiedImport) { Some(format!("Did you mean `type {name}`?")) } else { did_you_mean(name, value_constructors) }, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::ModuleAliasUsedAsName { location, name } => { let text = wrap( "Modules are not values, so you cannot assign them \to variables, pass them to functions, or anything else that you would do with a value.", ); Diagnostic { title: format!("Module `{name}` used as a value"), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::IncorrectNumClausePatterns { location, expected, given, } => { let subject = if *expected == 1 { "subject" } else { "subjects" }; let pattern = if *expected == 1 { "pattern" } else { "patterns" }; let text = wrap_format!( "This case expression has {expected} {subject}, \but this pattern matches {given}.Each clause must have a pattern for every subject value.", ); Diagnostic { title: "Incorrect number of patterns".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("Expected {expected} {pattern}, got {given}")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::NonLocalClauseGuardVariable { location, name } => { let text = wrap_format!( "Variables used in guards must be either defined in the \function, or be an argument to the function. The variable \`{name}` is not defined locally.", ); Diagnostic { title: "Invalid guard variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Is not locally defined".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::ExtraVarInAlternativePattern { location, name } => { let text = wrap_format!( "All alternative patterns must define the same variables as \the initial pattern. This variable `{name}` has not been previously defined.", ); Diagnostic { title: "Extra alternative pattern variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Has not been previously defined".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::MissingVarInAlternativePattern { location, name } => { let text = wrap_format!( "All alternative patterns must define the same variables \as the initial pattern, but the `{name}` variable is missing.", ); Diagnostic { title: "Missing alternative pattern variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This does not define all required variables".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::DuplicateVarInPattern { location, name } => { let text = wrap_format!( "Variables can only be used once per pattern. This \variable `{name}` appears multiple times.If you used the same variable twice deliberately in order to check for equality \please use a guard clause instead.e.g. (x, y) if x == y -> ...", ); Diagnostic { title: "Duplicate variable in pattern".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This has already been used".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::OutOfBoundsTupleIndex { location, size: 0, .. } => Diagnostic { title: "Out of bounds tuple index".into(), text: "This tuple has no elements so it cannot be indexed at all.".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::OutOfBoundsTupleIndex { location, index, size, } => { let text = wrap_format!( "The index being accessed for this tuple is {}, but this \tuple has {} elements so the highest valid index is {}.", index, size, size - 1, ); Diagnostic { title: "Out of bounds tuple index".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This index is too large".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::NotATuple { location, given } => { let mut printer = Printer::new(names); let text = format!( "To index into this value it needs to be a tuple, \however it has this type:
{}", printer.print_type(given), ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a tuple".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::NotATupleUnbound { location } => { let text = wrap( "To index into a tuple we need to \know its size, but we don't know anything about this type yet. \Please add some type annotations so we can continue.", ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("What type is this?".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::RecordAccessUnknownType { location } => { let text = wrap( "In order to access a record field \we need to know what type it is, but I can't tell \the type here. Try adding type annotations to your \function and try again.", ); Diagnostic { title: "Unknown type for record access".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I don't know what type this is".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::BitArraySegmentError { error, location } => { let (label, mut extra) = match error { bit_array::ErrorType::ConflictingTypeOptions { existing_type } => ( "This is an extra type specifier", vec![format!( "Hint: This segment already has the type {existing_type}." )], ),
bit_array::ErrorType::ConflictingSignednessOptions { existing_signed } => ( "This is an extra signedness specifier", vec![format!( "Hint: This segment already has a \signedness of {existing_signed}." )], ),
bit_array::ErrorType::ConflictingEndiannessOptions { existing_endianness, } => ( "This is an extra endianness specifier", vec![format!( "Hint: This segment already has an \endianness of {existing_endianness}." )], ),
bit_array::ErrorType::ConflictingSizeOptions => ( "This is an extra size specifier", vec!["Hint: This segment already has a size.".into()], ),
bit_array::ErrorType::ConflictingUnitOptions => ( "This is an extra unit specifier", vec!["Hint: A BitArray segment can have at most 1 unit.".into()], ),
bit_array::ErrorType::FloatWithSize => ( "Invalid float size", vec!["Hint: floats have an exact size of 16/32/64 bits.".into()], ),
bit_array::ErrorType::InvalidEndianness => ( "This option is invalid here", vec![wrap( "Hint: signed and unsigned \can only be used with int, float, utf16 and utf32 types.", )], ),
bit_array::ErrorType::OptionNotAllowedInValue => ( "This option is only allowed in BitArray patterns", vec!["Hint: This option has no effect in BitArray values.".into()], ),
bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => ( "Signedness is only valid with int types", vec![format!("Hint: This segment has a type of {type_}")], ), bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => ( "Size cannot be specified here", vec![format!("Hint: {type_} segments have an automatic size.")], ), bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => ( "Unit cannot be specified here", vec![wrap(&format!( "Hint: {type_} segments \are sized based on their value and cannot have a unit." ))], ), bit_array::ErrorType::VariableUtfSegmentInPattern => ( "This cannot be a variable", vec![wrap( "Hint: in patterns utf8, utf16, and \utf32 must be an exact string.", )], ), bit_array::ErrorType::SegmentMustHaveSize => ( "This segment has no size", vec![wrap( "Hint: Bit array segments without \a size are only allowed at the end of a bin pattern.", )], ), bit_array::ErrorType::UnitMustHaveSize => ( "This needs an explicit size", vec!["Hint: If you specify unit() you must also specify size().".into()], ), bit_array::ErrorType::ConstantSizeNotPositive => { ("A constant size must be a positive number", vec![]) } bit_array::ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::NativeEndianness, } => ( "Unsupported endianness", vec![wrap_format!( "The {target} target does not support the `native` \endianness option.", target = target.as_presentable_str(), )], ), bit_array::ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::UtfCodepointPattern, } => ( "UTF-codepoint pattern matching is not supported", vec![wrap_format!( "The {target} target does not support \UTF-codepoint pattern matching.", target = target.as_presentable_str(), )], ), }; extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into()); let text = extra.join("\n"); Diagnostic { title: "Invalid bit array segment".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label.into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic { title: "Invalid record constructor".into(), text: "Only record constructors can be used with the update syntax.".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a record constructor".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::RecordUpdateVariantWithNoFields { location } => Diagnostic { title: "Invalid record constructor".into(), text: wrap( "Only constructors with at least one labelled \field can be used with the update syntax.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This constructor has no labelled fields".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::UnexpectedTypeHole { location } => Diagnostic { title: "Unexpected type hole".into(), text: "We need to know the exact type here so type holes cannot be used.".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I need to know what this is".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::ReservedModuleName { name } => { let text = format!( "The module name `{name}` is reserved.Try a different name for this module." ); Diagnostic { title: "Reserved module name".into(), text, hint: None, location: None, level: Level::Error, } }
TypeError::KeywordInModuleName { name, keyword } => { let text = wrap_format!( "The module name `{name}` contains the keyword `{keyword}`, \so importing it would be a syntax error.Try a different name for this module." ); Diagnostic { title: "Invalid module name".into(), text, hint: None, location: None, level: Level::Error, } }
TypeError::NotExhaustivePatternMatch { location, unmatched, kind, } => { let mut text = match kind { PatternMatchKind::Case => { "This case expression does not match all possibilities.Each constructor must have a pattern that matches it orelse it could crash." } PatternMatchKind::Assignment => { "This assignment does not match all possibilities.Either use a case expression with patterns for each possiblevalue, or use `let assert` rather than `let`." } } .to_string();
text.push_str("\n\nThese values are not matched:\n\n"); for unmatched in unmatched { text.push_str(" - "); text.push_str(unmatched); text.push('\n'); } Diagnostic { title: "Not exhaustive pattern match".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic { title: "Argument name already used".into(), text: format!("Two `{name}` arguments have been defined for this function."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::UnlabelledAfterlabelled { location } => Diagnostic { title: "Unlabelled argument after labelled argument".into(), text: wrap("All unlabelled arguments must come before any labelled arguments."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::RecursiveTypeAlias { location, cycle } => { let mut text = "This type alias is defined in terms of itself.\n".into(); write_cycle(&mut text, cycle); text.push_str( "If we tried to compile this recursive type it would expandforever in a loop, and we'd never get the final type.", ); Diagnostic { title: "Type cycle".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::ExternalMissingAnnotation { location, kind } => { let kind = match kind { MissingAnnotation::Parameter => "parameter", MissingAnnotation::Return => "return", }; let text = format!( "A {kind} annotation is missing from this function.
Functions with external implementations must have type annotationsso we can tell what type of values they accept and return.", ); Diagnostic { title: "Missing type annotation".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::NoImplementation { location } => { let text = "We can't compile this function as it doesn't have animplementation. Add a body or an external implementationusing the `@external` attribute." .into(); Diagnostic { title: "Function without an implementation".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::InvalidExternalJavascriptModule { location, name, module, } => { let text = wrap_format!( "The function `{name}` has an external JavaScript \implementation but the module path `{module}` is not valid." ); Diagnostic { title: "Invalid JavaScript module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::InvalidExternalJavascriptFunction { location, name, function, } => { let text = wrap_format!( "The function `{name}` has an external JavaScript \implementation but the function name `{function}` is not valid." ); Diagnostic { title: "Invalid JavaScript function".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::InexhaustiveLetAssignment { location, missing } => { let mut text = wrap( "This assignment uses a pattern that does not \match all possible values. If one of the other values \is used then the assignment will crash.
The missing patterns are:\n", ); for missing in missing { text.push_str("\n "); text.push_str(missing); }
Diagnostic { title: "Inexhaustive pattern".into(), text, hint: Some("Use a more general pattern or use `let assert` instead.".into()), level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } }
TypeError::InexhaustiveCaseExpression { location, missing } => { let mut text = wrap( "This case expression does not have a pattern \for all possible values. If it is run on one of the \values without a pattern then it will crash.
The missing patterns are:\n", ); for missing in missing { text.push_str("\n "); text.push_str(missing); } Diagnostic { title: "Inexhaustive patterns".into(), text, hint: None, level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } }
TypeError::MissingCaseBody { location } => { let text = wrap("This case expression is missing its body."); Diagnostic { title: "Missing case body".into(), text, hint: None, level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } }
TypeError::UnsupportedExpressionTarget { location, target: current_target, } => { let text = wrap_format!( "This value is not available as it is defined using externals, \and there is no implementation for the {} target.", match current_target { Target::Erlang => "Erlang", Target::JavaScript => "JavaScript", Target::Wasm => "Wasm", } ); let hint = wrap("Did you mean to build for a different target?"); Diagnostic { title: "Unsupported target".into(), text, hint: Some(hint), level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } }
TypeError::UnsupportedPublicFunctionTarget { location, name, target, } => { let target = match target { Target::Erlang => "Erlang", Target::JavaScript => "JavaScript", Target::Wasm => "Wasm", }; let text = wrap_format!( "The `{name}` function is public but doesn't have an \implementation for the {target} target. All public functions of a package \must be able to compile for a module to be valid." ); Diagnostic { title: "Unsupported target".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } }
TypeError::UnusedTypeAliasParameter { location, name } => { let text = wrap_format!("The type variable `{name}` is unused. It can be safely removed.",); Diagnostic { title: "Unused type parameter".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } }
TypeError::DuplicateTypeParameter { location, name } => { let text = wrap_format!( "This definition has multiple type parameters named `{name}`.Rename or remove one of them.", ); Diagnostic { title: "Duplicate type parameter".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } }
TypeError::NotFnInUse { location, type_ } => { let mut printer = Printer::new(names); let text = wrap_format!( "In a use expression, there should be a function on \the right hand side of `<-`, but this value has type:
{}
See: https://tour.gleam.run/advanced-features/use/", printer.print_type(type_) );
Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UseFnDoesntTakeCallback { location, actual_type: None, } | TypeError::UseFnIncorrectArity { location, expected: 0, given: 1, } => { let text = wrap( "The function on the right of `<-` here \takes no arguments, but it has to take at least \one argument, a callback function.
See: https://tour.gleam.run/advanced-features/use/", ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Expected no arguments, got 1".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UseFnIncorrectArity { location, expected, given, } => { let expected_string = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), }; let supplied_arguments = given - 1; let supplied_arguments_string = match supplied_arguments { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{given} arguments"), }; let label = format!("Expected {expected_string}, got {given}"); let mut text: String = format!( "The function on the right of `<-` \here takes {expected_string}.\n" );
if expected > given { if supplied_arguments == 0 { text.push_str( "The only argument that was supplied is \the `use` callback function.\n", ); } else { text.push_str("You supplied "); text.push_str(&supplied_arguments_string); text.push_str(" and the final one is the `use` callback function.\n"); } } else { text.push_str( "All the arguments have already been supplied, \so it cannot take the `use` callback function as a final argument.\n", ); }
text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/");
Diagnostic { title: "Incorrect arity".into(), text: wrap(&text), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UseFnDoesntTakeCallback { location, actual_type: Some(actual), } => { let mut printer = Printer::new(names); let text = wrap_format!( "The function on the right hand side of `<-` \has to take a callback function as its last argument. \But the last argument of this function has type:
{}
See: https://tour.gleam.run/advanced-features/use/", printer.print_type(actual) ); Diagnostic { title: "Type mismatch".into(), text: wrap(&text), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::UseCallbackIncorrectArity { pattern_location, call_location, expected, given, } => { let expected = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), };
let specified = match given { 0 => "none were provided".into(), 1 => "1 was provided".into(), _ => format!("{given} were provided"), };
let text = wrap_format!( "This function takes a callback that expects {expected}. \But {specified} on the left hand side of `<-`.
See: https://tour.gleam.run/advanced-features/use/" ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *call_location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some(format!("Expected {expected}, got {given}")), span: *pattern_location, }, }], }), } }
TypeError::BadName { location, name, kind, } => { let kind_str = kind.as_str(); let label = format!("This is not a valid {} name", kind_str.to_lowercase()); let hint = match kind { Named::Type | Named::TypeAlias | Named::CustomTypeVariant => { format!( "{} names start with an uppercase \letter and contain only lowercase letters, numbers, \and uppercase letters.Try: {}", kind_str, to_upper_camel_case(name) ) } Named::Variable | Named::TypeVariable | Named::Argument | Named::Label | Named::Constant | Named::Function => format!( "{} names start with a lowercase letter \and contain a-z, 0-9, or _.Try: {}", kind_str, to_snake_case(name) ), Named::Discard => format!( "{} names start with _ and contain \a-z, 0-9, or _.Try: _{}", kind_str, to_snake_case(name) ), };
Diagnostic { title: format!("Invalid {} name", kind_str.to_lowercase()), text: "".into(), hint: Some(hint), level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::AllVariantsDeprecated { location } => { let text = String::from( "Consider deprecating the type as a whole.
@deprecated(\"message\") type Wibble { Wobble1 Wobble2 }", ); Diagnostic { title: "All variants of custom type deprecated.".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::DeprecatedVariantOnDeprecatedType { location } => { let text = wrap( "This custom type has already been deprecated, so deprecating \one of its variants does nothing.Consider removing the deprecation attribute on the variant.", );
Diagnostic { title: "Custom type already deprecated".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } }
TypeError::EchoWithNoFollowingExpression { location } => Diagnostic { title: "Invalid echo use".to_string(), text: wrap("The `echo` keyword should be followed by a value to print."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I was expecting a value after this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::StringConcatenationWithAddInt { location } => Diagnostic { title: "Type mismatch".to_string(), text: wrap( "The + operator can only be used on Ints.To join two strings together you can use the <> operator.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Use <> instead".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::IntOperatorOnFloats { location, operator } => Diagnostic { title: "Type mismatch".to_string(), text: wrap_format!("The {} operator can only be used on Ints.", operator.name()), hint: None, level: Level::Error, location: Some(Location { label: Label { text: operator .float_equivalent() .map(|operator| format!("Use {} instead", operator.name())), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::FloatOperatorOnInts { location, operator } => Diagnostic { title: "Type mismatch".to_string(), text: wrap_format!( "The {} operator can only be used on Floats.", operator.name() ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: operator .int_equivalent() .map(|operator| format!("Use {} instead", operator.name())), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::DoubleVariableAssignmentInBitArray { location } => Diagnostic { title: "Double variable assignment".to_string(), text: wrap( "This pattern assigns to two different variables \at once, which is not possible in bit arrays.", ), hint: Some(wrap("Remove the `as` assignment.")), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::NonUtf8StringAssignmentInBitArray { location } => Diagnostic { title: "Non UTF-8 string assignment".to_string(), text: wrap( "This pattern assigns a non UTF-8 string to a \variable in a bit array. This is planned to be supported in the future, but we are \unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 \and explain your usecase for this pattern, and how you would expect it to behave.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::PrivateOpaqueType { location } => Diagnostic { title: "Private opaque type".to_string(), text: wrap("Only a public type can be opaque."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("You can safely remove this.".to_string()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::SrcImportingDevDependency { location, importing_module, imported_module, package, } => Diagnostic { title: "App importing dev dependency".to_string(), text: wrap_format!( "The application module `{importing_module}` is \importing the module `{imported_module}`, but `{package}`, the package it \belongs to, is a dev dependency.
Dev dependencies are not included in production builds so application \modules should not import them. Perhaps change `{package}` to a regular dependency." ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::ExternalTypeWithConstructors { location } => Diagnostic { title: "External type with constructors".to_string(), text: wrap_format!( "This type is annotated with the `@external` annotation, \but it has constructors. The `@external` annotation is only for external types \with no constructors." ), hint: Some("Remove the `@external` annotation".into()), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::LowercaseBoolPattern { location } => Diagnostic { title: "Lowercase bool pattern".to_string(), text: "".into(), hint: Some( "In Gleam bool literals are `True` and `False`.See: https://tour.gleam.run/basics/bools/" .into(), ), level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a bool".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), },
TypeError::TodoConstant { location } => Diagnostic { title: "Constant todo found".to_string(), text: wrap( "This code will crash if it is run. \Be sure to finish it before running your program.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This code is incomplete".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, })}
fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String { use std::io::ErrorKind; match kind { ErrorKind::NotFound => "Could not find the stdio stream".into(), ErrorKind::PermissionDenied => "Permission was denied".into(), ErrorKind::ConnectionRefused => "Connection was refused".into(), ErrorKind::ConnectionReset => "Connection was reset".into(), ErrorKind::ConnectionAborted => "Connection was aborted".into(), ErrorKind::NotConnected => "Was not connected".into(), ErrorKind::AddrInUse => "The stream was already in use".into(), ErrorKind::AddrNotAvailable => "The stream was not available".into(), ErrorKind::BrokenPipe => "The pipe was broken".into(), ErrorKind::AlreadyExists => "A handle to the stream already exists".into(), ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(), ErrorKind::InvalidInput => "Some parameter was invalid".into(), ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(), ErrorKind::TimedOut => "The operation timed out".into(), ErrorKind::WriteZero => { "An attempt was made to write, but all bytes could not be written".into() } ErrorKind::Interrupted => "The operation was interrupted".into(), ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(), ErrorKind::HostUnreachable | ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown | ErrorKind::NotADirectory | ErrorKind::IsADirectory | ErrorKind::DirectoryNotEmpty | ErrorKind::ReadOnlyFilesystem | ErrorKind::StaleNetworkFileHandle | ErrorKind::StorageFull | ErrorKind::NotSeekable | ErrorKind::QuotaExceeded | ErrorKind::FileTooLarge | ErrorKind::ResourceBusy | ErrorKind::ExecutableFileBusy | ErrorKind::Deadlock | ErrorKind::CrossesDevices | ErrorKind::TooManyLinks | ErrorKind::InvalidFilename | ErrorKind::ArgumentListTooLong | ErrorKind::Unsupported | ErrorKind::OutOfMemory | ErrorKind::Other | _ => "An unknown error occurred".into(), }}
fn write_cycle(buffer: &mut String, cycle: &[EcoString]) { buffer.push_str( " ┌─────┐\n", ); for (index, name) in cycle.iter().enumerate() { if index != 0 { buffer.push_str(" │ ↓\n"); } buffer.push_str(" │ "); buffer.push_str(name); buffer.push('\n'); } buffer.push_str(" └─────┘\n");}
fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option<String> { match op { BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")), BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")), BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")), BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")), BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")), BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")), BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")), BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")),
BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")), BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")), BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")), BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")), BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")), BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")), BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")), BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")),
BinOp::AddInt if given.is_string() => Some(hint_string_message()), BinOp::AddFloat if given.is_string() => Some(hint_string_message()),
BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => None, }}
fn hint_wrap_value_in_result(expected: &Arc<Type>, given: &Arc<Type>) -> Option<String> { let expected = collapse_links(expected.clone()); let (expected_ok_type, expected_error_type) = expected.result_types()?;
if given.same_as(expected_ok_type.as_ref()) { Some("Did you mean to wrap this in an `Ok`?".into()) } else if given.same_as(expected_error_type.as_ref()) { Some("Did you mean to wrap this in an `Error`?".into()) } else { None }}
fn hint_numeric_message(alt: &str, type_: &str) -> String { format!("The {alt} operator can be used with {type_}s\n")}
fn hint_string_message() -> String { wrap("Strings can be joined using the `<>` operator.")}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct Unformatted { pub source: Utf8PathBuf, pub destination: Utf8PathBuf, pub input: EcoString, pub output: String,}
pub fn wrap(text: &str) -> String { let mut result = String::with_capacity(text.len());
for (i, line) in wrap_text(text, 75).iter().enumerate() { if i > 0 { result.push('\n'); } result.push_str(line); }
result}
fn wrap_text(text: &str, width: usize) -> Vec<Cow<'_, str>> { let mut lines: Vec<Cow<'_, str>> = Vec::new(); for line in text.split('\n') { // check if line needs to be broken match line.len() > width { false => lines.push(Cow::from(line)), true => { let mut new_lines = break_line(line, width); lines.append(&mut new_lines); } } }
lines}
fn break_line(line: &str, width: usize) -> Vec<Cow<'_, str>> { let mut lines: Vec<Cow<'_, str>> = Vec::new(); let mut newline = String::from("");
// split line by spaces for (i, word) in line.split(' ').enumerate() { let is_new_line = i < 1 || newline.is_empty();
let can_add_word = match is_new_line { true => newline.len() + word.len() <= width, // +1 accounts for space added before word false => newline.len() + (word.len() + 1) <= width, };
if can_add_word { if !is_new_line { newline.push(' '); } newline.push_str(word); } else { // word too big, save existing line if present if !newline.is_empty() { // save current line and reset it lines.push(Cow::from(newline.to_owned())); newline.clear(); }
// then save word to a new line or break it match word.len() > width { false => newline.push_str(word), true => { let (mut newlines, remainder) = break_word(word, width); lines.append(&mut newlines); newline.push_str(remainder); } } } }
// save last line after loop finishes if !newline.is_empty() { lines.push(Cow::from(newline)); }
lines}
// breaks word into n lines based on width. Returns list of new lines and remainderfn break_word(word: &str, width: usize) -> (Vec<Cow<'_, str>>, &str) { let mut new_lines: Vec<Cow<'_, str>> = Vec::new(); // Split on a char boundary so a multi-byte UTF-8 char straddling `width` // doesn't cause a panic. let (first, mut remainder) = word.split_at(word.floor_char_boundary(width)); new_lines.push(Cow::from(first));
// split remainder until it's small enough while remainder.len() > width { let (first, second) = remainder.split_at(remainder.floor_char_boundary(width)); new_lines.push(Cow::from(first)); remainder = second; }
(new_lines, remainder)}