| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940 |
- """The semantic analyzer.
- Bind names to definitions and do various other simple consistency
- checks. Populate symbol tables. The semantic analyzer also detects
- special forms which reuse generic syntax such as NamedTuple and
- cast(). Multiple analysis iterations may be needed to analyze forward
- references and import cycles. Each iteration "fills in" additional
- bindings and references until everything has been bound.
- For example, consider this program:
- x = 1
- y = x
- Here semantic analysis would detect that the assignment 'x = 1'
- defines a new variable, the type of which is to be inferred (in a
- later pass; type inference or type checking is not part of semantic
- analysis). Also, it would bind both references to 'x' to the same
- module-level variable (Var) node. The second assignment would also
- be analyzed, and the type of 'y' marked as being inferred.
- Semantic analysis of types is implemented in typeanal.py.
- See semanal_main.py for the top-level logic.
- Some important properties:
- * After semantic analysis is complete, no PlaceholderNode and
- PlaceholderType instances should remain. During semantic analysis,
- if we encounter one of these, the current target should be deferred.
- * A TypeInfo is only created once we know certain basic information about
- a type, such as the MRO, existence of a Tuple base class (e.g., for named
- tuples), and whether we have a TypedDict. We use a temporary
- PlaceholderNode node in the symbol table if some such information is
- missing.
- * For assignments, we only add a non-placeholder symbol table entry once
- we know the sort of thing being defined (variable, NamedTuple, type alias,
- etc.).
- * Every part of the analysis step must support multiple iterations over
- the same AST nodes, and each iteration must be able to fill in arbitrary
- things that were missing or incomplete in previous iterations.
- * Changes performed by the analysis need to be reversible, since mypy
- daemon strips and reuses existing ASTs (to improve performance and/or
- reduce memory use).
- """
- from __future__ import annotations
- from contextlib import contextmanager
- from typing import Any, Callable, Collection, Final, Iterable, Iterator, List, TypeVar, cast
- from typing_extensions import TypeAlias as _TypeAlias
- from mypy import errorcodes as codes, message_registry
- from mypy.constant_fold import constant_fold_expr
- from mypy.errorcodes import ErrorCode
- from mypy.errors import Errors, report_internal_error
- from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
- from mypy.messages import (
- SUGGESTED_TEST_FIXTURES,
- TYPES_FOR_UNIMPORTED_HINTS,
- MessageBuilder,
- best_matches,
- pretty_seq,
- )
- from mypy.mro import MroError, calculate_mro
- from mypy.nodes import (
- ARG_NAMED,
- ARG_POS,
- ARG_STAR,
- ARG_STAR2,
- CONTRAVARIANT,
- COVARIANT,
- GDEF,
- IMPLICITLY_ABSTRACT,
- INVARIANT,
- IS_ABSTRACT,
- LDEF,
- MDEF,
- NOT_ABSTRACT,
- REVEAL_LOCALS,
- REVEAL_TYPE,
- RUNTIME_PROTOCOL_DECOS,
- ArgKind,
- AssertStmt,
- AssertTypeExpr,
- AssignmentExpr,
- AssignmentStmt,
- AwaitExpr,
- Block,
- BreakStmt,
- CallExpr,
- CastExpr,
- ClassDef,
- ComparisonExpr,
- ConditionalExpr,
- Context,
- ContinueStmt,
- DataclassTransformSpec,
- Decorator,
- DelStmt,
- DictExpr,
- DictionaryComprehension,
- EllipsisExpr,
- EnumCallExpr,
- Expression,
- ExpressionStmt,
- FakeExpression,
- ForStmt,
- FuncBase,
- FuncDef,
- FuncItem,
- GeneratorExpr,
- GlobalDecl,
- IfStmt,
- Import,
- ImportAll,
- ImportBase,
- ImportFrom,
- IndexExpr,
- LambdaExpr,
- ListComprehension,
- ListExpr,
- Lvalue,
- MatchStmt,
- MemberExpr,
- MypyFile,
- NamedTupleExpr,
- NameExpr,
- Node,
- NonlocalDecl,
- OperatorAssignmentStmt,
- OpExpr,
- OverloadedFuncDef,
- OverloadPart,
- ParamSpecExpr,
- PassStmt,
- PlaceholderNode,
- PromoteExpr,
- RaiseStmt,
- RefExpr,
- ReturnStmt,
- RevealExpr,
- SetComprehension,
- SetExpr,
- SliceExpr,
- StarExpr,
- Statement,
- StrExpr,
- SuperExpr,
- SymbolNode,
- SymbolTable,
- SymbolTableNode,
- TempNode,
- TryStmt,
- TupleExpr,
- TypeAlias,
- TypeAliasExpr,
- TypeApplication,
- TypedDictExpr,
- TypeInfo,
- TypeVarExpr,
- TypeVarLikeExpr,
- TypeVarTupleExpr,
- UnaryExpr,
- Var,
- WhileStmt,
- WithStmt,
- YieldExpr,
- YieldFromExpr,
- get_member_expr_fullname,
- get_nongen_builtins,
- implicit_module_attrs,
- is_final_node,
- type_aliases,
- type_aliases_source_versions,
- typing_extensions_aliases,
- )
- from mypy.options import TYPE_VAR_TUPLE, Options
- from mypy.patterns import (
- AsPattern,
- ClassPattern,
- MappingPattern,
- OrPattern,
- SequencePattern,
- StarredPattern,
- ValuePattern,
- )
- from mypy.plugin import (
- ClassDefContext,
- DynamicClassDefContext,
- Plugin,
- SemanticAnalyzerPluginInterface,
- )
- from mypy.plugins import dataclasses as dataclasses_plugin
- from mypy.reachability import (
- ALWAYS_FALSE,
- ALWAYS_TRUE,
- MYPY_FALSE,
- MYPY_TRUE,
- infer_condition_value,
- infer_reachability_of_if_statement,
- infer_reachability_of_match_statement,
- )
- from mypy.scope import Scope
- from mypy.semanal_enum import EnumCallAnalyzer
- from mypy.semanal_namedtuple import NamedTupleAnalyzer
- from mypy.semanal_newtype import NewTypeAnalyzer
- from mypy.semanal_shared import (
- ALLOW_INCOMPATIBLE_OVERRIDE,
- PRIORITY_FALLBACKS,
- SemanticAnalyzerInterface,
- calculate_tuple_fallback,
- find_dataclass_transform_spec,
- has_placeholder,
- parse_bool,
- require_bool_literal_argument,
- set_callable_name as set_callable_name,
- )
- from mypy.semanal_typeddict import TypedDictAnalyzer
- from mypy.tvar_scope import TypeVarLikeScope
- from mypy.typeanal import (
- SELF_TYPE_NAMES,
- TypeAnalyser,
- TypeVarLikeList,
- TypeVarLikeQuery,
- analyze_type_alias,
- check_for_explicit_any,
- detect_diverging_alias,
- find_self_type,
- fix_instance_types,
- has_any_from_unimported_type,
- no_subscript_builtin_alias,
- type_constructors,
- )
- from mypy.typeops import function_type, get_type_vars, try_getting_str_literals_from_type
- from mypy.types import (
- ASSERT_TYPE_NAMES,
- DATACLASS_TRANSFORM_NAMES,
- FINAL_DECORATOR_NAMES,
- FINAL_TYPE_NAMES,
- NEVER_NAMES,
- OVERLOAD_NAMES,
- OVERRIDE_DECORATOR_NAMES,
- PROTOCOL_NAMES,
- REVEAL_TYPE_NAMES,
- TPDICT_NAMES,
- TYPE_ALIAS_NAMES,
- TYPED_NAMEDTUPLE_NAMES,
- AnyType,
- CallableType,
- FunctionLike,
- Instance,
- LiteralType,
- NoneType,
- Overloaded,
- Parameters,
- ParamSpecType,
- PlaceholderType,
- ProperType,
- TrivialSyntheticTypeTranslator,
- TupleType,
- Type,
- TypeAliasType,
- TypedDictType,
- TypeOfAny,
- TypeType,
- TypeVarLikeType,
- TypeVarTupleType,
- TypeVarType,
- UnboundType,
- UnpackType,
- get_proper_type,
- get_proper_types,
- is_named_instance,
- remove_dups,
- )
- from mypy.types_utils import is_invalid_recursive_alias, store_argument_type
- from mypy.typevars import fill_typevars
- from mypy.util import (
- correct_relative_import,
- is_dunder,
- is_typeshed_file,
- module_prefix,
- unmangle,
- unnamed_function,
- )
- from mypy.visitor import NodeVisitor
- T = TypeVar("T")
- FUTURE_IMPORTS: Final = {
- "__future__.nested_scopes": "nested_scopes",
- "__future__.generators": "generators",
- "__future__.division": "division",
- "__future__.absolute_import": "absolute_import",
- "__future__.with_statement": "with_statement",
- "__future__.print_function": "print_function",
- "__future__.unicode_literals": "unicode_literals",
- "__future__.barry_as_FLUFL": "barry_as_FLUFL",
- "__future__.generator_stop": "generator_stop",
- "__future__.annotations": "annotations",
- }
- # Special cased built-in classes that are needed for basic functionality and need to be
- # available very early on.
- CORE_BUILTIN_CLASSES: Final = ["object", "bool", "function"]
- # Used for tracking incomplete references
- Tag: _TypeAlias = int
- class SemanticAnalyzer(
- NodeVisitor[None], SemanticAnalyzerInterface, SemanticAnalyzerPluginInterface
- ):
- """Semantically analyze parsed mypy files.
- The analyzer binds names and does various consistency checks for an
- AST. Note that type checking is performed as a separate pass.
- """
- __deletable__ = ["patches", "options", "cur_mod_node"]
- # Module name space
- modules: dict[str, MypyFile]
- # Global name space for current module
- globals: SymbolTable
- # Names declared using "global" (separate set for each scope)
- global_decls: list[set[str]]
- # Names declared using "nonlocal" (separate set for each scope)
- nonlocal_decls: list[set[str]]
- # Local names of function scopes; None for non-function scopes.
- locals: list[SymbolTable | None]
- # Whether each scope is a comprehension scope.
- is_comprehension_stack: list[bool]
- # Nested block depths of scopes
- block_depth: list[int]
- # TypeInfo of directly enclosing class (or None)
- _type: TypeInfo | None = None
- # Stack of outer classes (the second tuple item contains tvars).
- type_stack: list[TypeInfo | None]
- # Type variables bound by the current scope, be it class or function
- tvar_scope: TypeVarLikeScope
- # Per-module options
- options: Options
- # Stack of functions being analyzed
- function_stack: list[FuncItem]
- # Set to True if semantic analysis defines a name, or replaces a
- # placeholder definition. If some iteration makes no progress,
- # there can be at most one additional final iteration (see below).
- progress = False
- deferred = False # Set to true if another analysis pass is needed
- incomplete = False # Set to true if current module namespace is missing things
- # Is this the final iteration of semantic analysis (where we report
- # unbound names due to cyclic definitions and should not defer)?
- _final_iteration = False
- # These names couldn't be added to the symbol table due to incomplete deps.
- # Note that missing names are per module, _not_ per namespace. This means that e.g.
- # a missing name at global scope will block adding same name at a class scope.
- # This should not affect correctness and is purely a performance issue,
- # since it can cause unnecessary deferrals. These are represented as
- # PlaceholderNodes in the symbol table. We use this to ensure that the first
- # definition takes precedence even if it's incomplete.
- #
- # Note that a star import adds a special name '*' to the set, this blocks
- # adding _any_ names in the current file.
- missing_names: list[set[str]]
- # Callbacks that will be called after semantic analysis to tweak things.
- patches: list[tuple[int, Callable[[], None]]]
- loop_depth: list[int] # Depth of breakable loops
- cur_mod_id = "" # Current module id (or None) (phase 2)
- _is_stub_file = False # Are we analyzing a stub file?
- _is_typeshed_stub_file = False # Are we analyzing a typeshed stub file?
- imports: set[str] # Imported modules (during phase 2 analysis)
- # Note: some imports (and therefore dependencies) might
- # not be found in phase 1, for example due to * imports.
- errors: Errors # Keeps track of generated errors
- plugin: Plugin # Mypy plugin for special casing of library features
- statement: Statement | None = None # Statement/definition being analyzed
- # Mapping from 'async def' function definitions to their return type wrapped as a
- # 'Coroutine[Any, Any, T]'. Used to keep track of whether a function definition's
- # return type has already been wrapped, by checking if the function definition's
- # type is stored in this mapping and that it still matches.
- wrapped_coro_return_types: dict[FuncDef, Type] = {}
- def __init__(
- self,
- modules: dict[str, MypyFile],
- missing_modules: set[str],
- incomplete_namespaces: set[str],
- errors: Errors,
- plugin: Plugin,
- ) -> None:
- """Construct semantic analyzer.
- We reuse the same semantic analyzer instance across multiple modules.
- Args:
- modules: Global modules dictionary
- missing_modules: Modules that could not be imported encountered so far
- incomplete_namespaces: Namespaces that are being populated during semantic analysis
- (can contain modules and classes within the current SCC; mutated by the caller)
- errors: Report analysis errors using this instance
- """
- self.locals = [None]
- self.is_comprehension_stack = [False]
- # Saved namespaces from previous iteration. Every top-level function/method body is
- # analyzed in several iterations until all names are resolved. We need to save
- # the local namespaces for the top level function and all nested functions between
- # these iterations. See also semanal_main.process_top_level_function().
- self.saved_locals: dict[
- FuncItem | GeneratorExpr | DictionaryComprehension, SymbolTable
- ] = {}
- self.imports = set()
- self._type = None
- self.type_stack = []
- # Are the namespaces of classes being processed complete?
- self.incomplete_type_stack: list[bool] = []
- self.tvar_scope = TypeVarLikeScope()
- self.function_stack = []
- self.block_depth = [0]
- self.loop_depth = [0]
- self.errors = errors
- self.modules = modules
- self.msg = MessageBuilder(errors, modules)
- self.missing_modules = missing_modules
- self.missing_names = [set()]
- # These namespaces are still in process of being populated. If we encounter a
- # missing name in these namespaces, we need to defer the current analysis target,
- # since it's possible that the name will be there once the namespace is complete.
- self.incomplete_namespaces = incomplete_namespaces
- self.all_exports: list[str] = []
- # Map from module id to list of explicitly exported names (i.e. names in __all__).
- self.export_map: dict[str, list[str]] = {}
- self.plugin = plugin
- # If True, process function definitions. If False, don't. This is used
- # for processing module top levels in fine-grained incremental mode.
- self.recurse_into_functions = True
- self.scope = Scope()
- # Trace line numbers for every file where deferral happened during analysis of
- # current SCC or top-level function.
- self.deferral_debug_context: list[tuple[str, int]] = []
- # This is needed to properly support recursive type aliases. The problem is that
- # Foo[Bar] could mean three things depending on context: a target for type alias,
- # a normal index expression (including enum index), or a type application.
- # The latter is particularly problematic as it can falsely create incomplete
- # refs while analysing rvalues of type aliases. To avoid this we first analyse
- # rvalues while temporarily setting this to True.
- self.basic_type_applications = False
- # Used to temporarily enable unbound type variables in some contexts. Namely,
- # in base class expressions, and in right hand sides of type aliases. Do not add
- # new uses of this, as this may cause leaking `UnboundType`s to type checking.
- self.allow_unbound_tvars = False
- # mypyc doesn't properly handle implementing an abstractproperty
- # with a regular attribute so we make them properties
- @property
- def type(self) -> TypeInfo | None:
- return self._type
- @property
- def is_stub_file(self) -> bool:
- return self._is_stub_file
- @property
- def is_typeshed_stub_file(self) -> bool:
- return self._is_typeshed_stub_file
- @property
- def final_iteration(self) -> bool:
- return self._final_iteration
- @contextmanager
- def allow_unbound_tvars_set(self) -> Iterator[None]:
- old = self.allow_unbound_tvars
- self.allow_unbound_tvars = True
- try:
- yield
- finally:
- self.allow_unbound_tvars = old
- #
- # Preparing module (performed before semantic analysis)
- #
- def prepare_file(self, file_node: MypyFile) -> None:
- """Prepare a freshly parsed file for semantic analysis."""
- if "builtins" in self.modules:
- file_node.names["__builtins__"] = SymbolTableNode(GDEF, self.modules["builtins"])
- if file_node.fullname == "builtins":
- self.prepare_builtins_namespace(file_node)
- if file_node.fullname == "typing":
- self.prepare_typing_namespace(file_node, type_aliases)
- if file_node.fullname == "typing_extensions":
- self.prepare_typing_namespace(file_node, typing_extensions_aliases)
- def prepare_typing_namespace(self, file_node: MypyFile, aliases: dict[str, str]) -> None:
- """Remove dummy alias definitions such as List = TypeAlias(object) from typing.
- They will be replaced with real aliases when corresponding targets are ready.
- """
- # This is all pretty unfortunate. typeshed now has a
- # sys.version_info check for OrderedDict, and we shouldn't
- # take it out, because it is correct and a typechecker should
- # use that as a source of truth. But instead we rummage
- # through IfStmts to remove the info first. (I tried to
- # remove this whole machinery and ran into issues with the
- # builtins/typing import cycle.)
- def helper(defs: list[Statement]) -> None:
- for stmt in defs.copy():
- if isinstance(stmt, IfStmt):
- for body in stmt.body:
- helper(body.body)
- if stmt.else_body:
- helper(stmt.else_body.body)
- if (
- isinstance(stmt, AssignmentStmt)
- and len(stmt.lvalues) == 1
- and isinstance(stmt.lvalues[0], NameExpr)
- ):
- # Assignment to a simple name, remove it if it is a dummy alias.
- if f"{file_node.fullname}.{stmt.lvalues[0].name}" in aliases:
- defs.remove(stmt)
- helper(file_node.defs)
- def prepare_builtins_namespace(self, file_node: MypyFile) -> None:
- """Add certain special-cased definitions to the builtins module.
- Some definitions are too special or fundamental to be processed
- normally from the AST.
- """
- names = file_node.names
- # Add empty definition for core built-in classes, since they are required for basic
- # operation. These will be completed later on.
- for name in CORE_BUILTIN_CLASSES:
- cdef = ClassDef(name, Block([])) # Dummy ClassDef, will be replaced later
- info = TypeInfo(SymbolTable(), cdef, "builtins")
- info._fullname = f"builtins.{name}"
- names[name] = SymbolTableNode(GDEF, info)
- bool_info = names["bool"].node
- assert isinstance(bool_info, TypeInfo)
- bool_type = Instance(bool_info, [])
- special_var_types: list[tuple[str, Type]] = [
- ("None", NoneType()),
- # reveal_type is a mypy-only function that gives an error with
- # the type of its arg.
- ("reveal_type", AnyType(TypeOfAny.special_form)),
- # reveal_locals is a mypy-only function that gives an error with the types of
- # locals
- ("reveal_locals", AnyType(TypeOfAny.special_form)),
- ("True", bool_type),
- ("False", bool_type),
- ("__debug__", bool_type),
- ]
- for name, typ in special_var_types:
- v = Var(name, typ)
- v._fullname = f"builtins.{name}"
- file_node.names[name] = SymbolTableNode(GDEF, v)
- #
- # Analyzing a target
- #
- def refresh_partial(
- self,
- node: MypyFile | FuncDef | OverloadedFuncDef,
- patches: list[tuple[int, Callable[[], None]]],
- final_iteration: bool,
- file_node: MypyFile,
- options: Options,
- active_type: TypeInfo | None = None,
- ) -> None:
- """Refresh a stale target in fine-grained incremental mode."""
- self.patches = patches
- self.deferred = False
- self.incomplete = False
- self._final_iteration = final_iteration
- self.missing_names[-1] = set()
- with self.file_context(file_node, options, active_type):
- if isinstance(node, MypyFile):
- self.refresh_top_level(node)
- else:
- self.recurse_into_functions = True
- self.accept(node)
- del self.patches
- def refresh_top_level(self, file_node: MypyFile) -> None:
- """Reanalyze a stale module top-level in fine-grained incremental mode."""
- self.recurse_into_functions = False
- self.add_implicit_module_attrs(file_node)
- for d in file_node.defs:
- self.accept(d)
- if file_node.fullname == "typing":
- self.add_builtin_aliases(file_node)
- if file_node.fullname == "typing_extensions":
- self.add_typing_extension_aliases(file_node)
- self.adjust_public_exports()
- self.export_map[self.cur_mod_id] = self.all_exports
- self.all_exports = []
- def add_implicit_module_attrs(self, file_node: MypyFile) -> None:
- """Manually add implicit definitions of module '__name__' etc."""
- str_type: Type | None = self.named_type_or_none("builtins.str")
- if str_type is None:
- str_type = UnboundType("builtins.str")
- for name, t in implicit_module_attrs.items():
- if name == "__doc__":
- typ: Type = str_type
- elif name == "__path__":
- if not file_node.is_package_init_file():
- continue
- # Need to construct the type ourselves, to avoid issues with __builtins__.list
- # not being subscriptable or typing.List not getting bound
- inst = self.named_type_or_none("builtins.list", [str_type])
- if inst is None:
- assert not self.final_iteration, "Cannot find builtins.list to add __path__"
- self.defer()
- return
- typ = inst
- elif name == "__annotations__":
- inst = self.named_type_or_none(
- "builtins.dict", [str_type, AnyType(TypeOfAny.special_form)]
- )
- if inst is None:
- assert (
- not self.final_iteration
- ), "Cannot find builtins.dict to add __annotations__"
- self.defer()
- return
- typ = inst
- else:
- assert t is not None, f"type should be specified for {name}"
- typ = UnboundType(t)
- existing = file_node.names.get(name)
- if existing is not None and not isinstance(existing.node, PlaceholderNode):
- # Already exists.
- continue
- an_type = self.anal_type(typ)
- if an_type:
- var = Var(name, an_type)
- var._fullname = self.qualified_name(name)
- var.is_ready = True
- self.add_symbol(name, var, dummy_context())
- else:
- self.add_symbol(
- name,
- PlaceholderNode(self.qualified_name(name), file_node, -1),
- dummy_context(),
- )
- def add_builtin_aliases(self, tree: MypyFile) -> None:
- """Add builtin type aliases to typing module.
- For historical reasons, the aliases like `List = list` are not defined
- in typeshed stubs for typing module. Instead we need to manually add the
- corresponding nodes on the fly. We explicitly mark these aliases as normalized,
- so that a user can write `typing.List[int]`.
- """
- assert tree.fullname == "typing"
- for alias, target_name in type_aliases.items():
- if type_aliases_source_versions[alias] > self.options.python_version:
- # This alias is not available on this Python version.
- continue
- name = alias.split(".")[-1]
- if name in tree.names and not isinstance(tree.names[name].node, PlaceholderNode):
- continue
- self.create_alias(tree, target_name, alias, name)
- def add_typing_extension_aliases(self, tree: MypyFile) -> None:
- """Typing extensions module does contain some type aliases.
- We need to analyze them as such, because in typeshed
- they are just defined as `_Alias()` call.
- Which is not supported natively.
- """
- assert tree.fullname == "typing_extensions"
- for alias, target_name in typing_extensions_aliases.items():
- name = alias.split(".")[-1]
- if name in tree.names and isinstance(tree.names[name].node, TypeAlias):
- continue # Do not reset TypeAliases on the second pass.
- # We need to remove any node that is there at the moment. It is invalid.
- tree.names.pop(name, None)
- # Now, create a new alias.
- self.create_alias(tree, target_name, alias, name)
- def create_alias(self, tree: MypyFile, target_name: str, alias: str, name: str) -> None:
- tag = self.track_incomplete_refs()
- n = self.lookup_fully_qualified_or_none(target_name)
- if n:
- if isinstance(n.node, PlaceholderNode):
- self.mark_incomplete(name, tree)
- else:
- # Found built-in class target. Create alias.
- target = self.named_type_or_none(target_name, [])
- assert target is not None
- # Transform List to List[Any], etc.
- fix_instance_types(target, self.fail, self.note, self.options)
- alias_node = TypeAlias(
- target,
- alias,
- line=-1,
- column=-1, # there is no context
- no_args=True,
- normalized=True,
- )
- self.add_symbol(name, alias_node, tree)
- elif self.found_incomplete_ref(tag):
- # Built-in class target may not ready yet -- defer.
- self.mark_incomplete(name, tree)
- else:
- # Test fixtures may be missing some builtin classes, which is okay.
- # Kill the placeholder if there is one.
- if name in tree.names:
- assert isinstance(tree.names[name].node, PlaceholderNode)
- del tree.names[name]
- def adjust_public_exports(self) -> None:
- """Adjust the module visibility of globals due to __all__."""
- if "__all__" in self.globals:
- for name, g in self.globals.items():
- # Being included in __all__ explicitly exports and makes public.
- if name in self.all_exports:
- g.module_public = True
- g.module_hidden = False
- # But when __all__ is defined, and a symbol is not included in it,
- # it cannot be public.
- else:
- g.module_public = False
- @contextmanager
- def file_context(
- self, file_node: MypyFile, options: Options, active_type: TypeInfo | None = None
- ) -> Iterator[None]:
- """Configure analyzer for analyzing targets within a file/class.
- Args:
- file_node: target file
- options: options specific to the file
- active_type: must be the surrounding class to analyze method targets
- """
- scope = self.scope
- self.options = options
- self.errors.set_file(file_node.path, file_node.fullname, scope=scope, options=options)
- self.cur_mod_node = file_node
- self.cur_mod_id = file_node.fullname
- with scope.module_scope(self.cur_mod_id):
- self._is_stub_file = file_node.path.lower().endswith(".pyi")
- self._is_typeshed_stub_file = is_typeshed_file(
- options.abs_custom_typeshed_dir, file_node.path
- )
- self.globals = file_node.names
- self.tvar_scope = TypeVarLikeScope()
- self.named_tuple_analyzer = NamedTupleAnalyzer(options, self)
- self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg)
- self.enum_call_analyzer = EnumCallAnalyzer(options, self)
- self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg)
- # Counter that keeps track of references to undefined things potentially caused by
- # incomplete namespaces.
- self.num_incomplete_refs = 0
- if active_type:
- self.incomplete_type_stack.append(False)
- scope.enter_class(active_type)
- self.enter_class(active_type.defn.info)
- for tvar in active_type.defn.type_vars:
- self.tvar_scope.bind_existing(tvar)
- yield
- if active_type:
- scope.leave_class()
- self.leave_class()
- self._type = None
- self.incomplete_type_stack.pop()
- del self.options
- #
- # Functions
- #
- def visit_func_def(self, defn: FuncDef) -> None:
- self.statement = defn
- # Visit default values because they may contain assignment expressions.
- for arg in defn.arguments:
- if arg.initializer:
- arg.initializer.accept(self)
- defn.is_conditional = self.block_depth[-1] > 0
- # Set full names even for those definitions that aren't added
- # to a symbol table. For example, for overload items.
- defn._fullname = self.qualified_name(defn.name)
- # We don't add module top-level functions to symbol tables
- # when we analyze their bodies in the second phase on analysis,
- # since they were added in the first phase. Nested functions
- # get always added, since they aren't separate targets.
- if not self.recurse_into_functions or len(self.function_stack) > 0:
- if not defn.is_decorated and not defn.is_overload:
- self.add_function_to_symbol_table(defn)
- if not self.recurse_into_functions:
- return
- with self.scope.function_scope(defn):
- self.analyze_func_def(defn)
- def analyze_func_def(self, defn: FuncDef) -> None:
- self.function_stack.append(defn)
- if defn.type:
- assert isinstance(defn.type, CallableType)
- has_self_type = self.update_function_type_variables(defn.type, defn)
- else:
- has_self_type = False
- self.function_stack.pop()
- if self.is_class_scope():
- # Method definition
- assert self.type is not None
- defn.info = self.type
- if defn.type is not None and defn.name in ("__init__", "__init_subclass__"):
- assert isinstance(defn.type, CallableType)
- if isinstance(get_proper_type(defn.type.ret_type), AnyType):
- defn.type = defn.type.copy_modified(ret_type=NoneType())
- self.prepare_method_signature(defn, self.type, has_self_type)
- # Analyze function signature
- with self.tvar_scope_frame(self.tvar_scope.method_frame()):
- if defn.type:
- self.check_classvar_in_signature(defn.type)
- assert isinstance(defn.type, CallableType)
- # Signature must be analyzed in the surrounding scope so that
- # class-level imported names and type variables are in scope.
- analyzer = self.type_analyzer()
- tag = self.track_incomplete_refs()
- result = analyzer.visit_callable_type(defn.type, nested=False)
- # Don't store not ready types (including placeholders).
- if self.found_incomplete_ref(tag) or has_placeholder(result):
- self.defer(defn)
- return
- assert isinstance(result, ProperType)
- if isinstance(result, CallableType):
- # type guards need to have a positional argument, to spec
- skip_self = self.is_class_scope() and not defn.is_static
- if result.type_guard and ARG_POS not in result.arg_kinds[skip_self:]:
- self.fail(
- "TypeGuard functions must have a positional argument",
- result,
- code=codes.VALID_TYPE,
- )
- # in this case, we just kind of just ... remove the type guard.
- result = result.copy_modified(type_guard=None)
- result = self.remove_unpack_kwargs(defn, result)
- if has_self_type and self.type is not None:
- info = self.type
- if info.self_type is not None:
- result.variables = [info.self_type] + list(result.variables)
- defn.type = result
- self.add_type_alias_deps(analyzer.aliases_used)
- self.check_function_signature(defn)
- self.check_paramspec_definition(defn)
- if isinstance(defn, FuncDef):
- assert isinstance(defn.type, CallableType)
- defn.type = set_callable_name(defn.type, defn)
- self.analyze_arg_initializers(defn)
- self.analyze_function_body(defn)
- if self.is_class_scope():
- assert self.type is not None
- # Mark protocol methods with empty bodies as implicitly abstract.
- # This makes explicit protocol subclassing type-safe.
- if (
- self.type.is_protocol
- and not self.is_stub_file # Bodies in stub files are always empty.
- and (not isinstance(self.scope.function, OverloadedFuncDef) or defn.is_property)
- and defn.abstract_status != IS_ABSTRACT
- and is_trivial_body(defn.body)
- ):
- defn.abstract_status = IMPLICITLY_ABSTRACT
- if (
- is_trivial_body(defn.body)
- and not self.is_stub_file
- and defn.abstract_status != NOT_ABSTRACT
- ):
- defn.is_trivial_body = True
- if (
- defn.is_coroutine
- and isinstance(defn.type, CallableType)
- and self.wrapped_coro_return_types.get(defn) != defn.type
- ):
- if defn.is_async_generator:
- # Async generator types are handled elsewhere
- pass
- else:
- # A coroutine defined as `async def foo(...) -> T: ...`
- # has external return type `Coroutine[Any, Any, T]`.
- any_type = AnyType(TypeOfAny.special_form)
- ret_type = self.named_type_or_none(
- "typing.Coroutine", [any_type, any_type, defn.type.ret_type]
- )
- assert ret_type is not None, "Internal error: typing.Coroutine not found"
- defn.type = defn.type.copy_modified(ret_type=ret_type)
- self.wrapped_coro_return_types[defn] = defn.type
- def remove_unpack_kwargs(self, defn: FuncDef, typ: CallableType) -> CallableType:
- if not typ.arg_kinds or typ.arg_kinds[-1] is not ArgKind.ARG_STAR2:
- return typ
- last_type = typ.arg_types[-1]
- if not isinstance(last_type, UnpackType):
- return typ
- last_type = get_proper_type(last_type.type)
- if not isinstance(last_type, TypedDictType):
- self.fail("Unpack item in ** argument must be a TypedDict", defn)
- new_arg_types = typ.arg_types[:-1] + [AnyType(TypeOfAny.from_error)]
- return typ.copy_modified(arg_types=new_arg_types)
- overlap = set(typ.arg_names) & set(last_type.items)
- # It is OK for TypedDict to have a key named 'kwargs'.
- overlap.discard(typ.arg_names[-1])
- if overlap:
- overlapped = ", ".join([f'"{name}"' for name in overlap])
- self.fail(f"Overlap between argument names and ** TypedDict items: {overlapped}", defn)
- new_arg_types = typ.arg_types[:-1] + [AnyType(TypeOfAny.from_error)]
- return typ.copy_modified(arg_types=new_arg_types)
- # OK, everything looks right now, mark the callable type as using unpack.
- new_arg_types = typ.arg_types[:-1] + [last_type]
- return typ.copy_modified(arg_types=new_arg_types, unpack_kwargs=True)
- def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: bool) -> None:
- """Check basic signature validity and tweak annotation of self/cls argument."""
- # Only non-static methods are special, as well as __new__.
- functype = func.type
- if func.name == "__new__":
- func.is_static = True
- if not func.is_static or func.name == "__new__":
- if func.name in ["__init_subclass__", "__class_getitem__"]:
- func.is_class = True
- if not func.arguments:
- self.fail(
- 'Method must have at least one argument. Did you forget the "self" argument?',
- func,
- )
- elif isinstance(functype, CallableType):
- self_type = get_proper_type(functype.arg_types[0])
- if isinstance(self_type, AnyType):
- if has_self_type:
- assert self.type is not None and self.type.self_type is not None
- leading_type: Type = self.type.self_type
- else:
- leading_type = fill_typevars(info)
- if func.is_class or func.name == "__new__":
- leading_type = self.class_type(leading_type)
- func.type = replace_implicit_first_type(functype, leading_type)
- elif has_self_type and isinstance(func.unanalyzed_type, CallableType):
- if not isinstance(get_proper_type(func.unanalyzed_type.arg_types[0]), AnyType):
- if self.is_expected_self_type(
- self_type, func.is_class or func.name == "__new__"
- ):
- # This error is off by default, since it is explicitly allowed
- # by the PEP 673.
- self.fail(
- 'Redundant "Self" annotation for the first method argument',
- func,
- code=codes.REDUNDANT_SELF_TYPE,
- )
- else:
- self.fail(
- "Method cannot have explicit self annotation and Self type", func
- )
- elif has_self_type:
- self.fail("Static methods cannot use Self type", func)
- def is_expected_self_type(self, typ: Type, is_classmethod: bool) -> bool:
- """Does this (analyzed or not) type represent the expected Self type for a method?"""
- assert self.type is not None
- typ = get_proper_type(typ)
- if is_classmethod:
- if isinstance(typ, TypeType):
- return self.is_expected_self_type(typ.item, is_classmethod=False)
- if isinstance(typ, UnboundType):
- sym = self.lookup_qualified(typ.name, typ, suppress_errors=True)
- if (
- sym is not None
- and (
- sym.fullname == "typing.Type"
- or (
- sym.fullname == "builtins.type"
- and (
- self.is_stub_file
- or self.is_future_flag_set("annotations")
- or self.options.python_version >= (3, 9)
- )
- )
- )
- and typ.args
- ):
- return self.is_expected_self_type(typ.args[0], is_classmethod=False)
- return False
- if isinstance(typ, TypeVarType):
- return typ == self.type.self_type
- if isinstance(typ, UnboundType):
- sym = self.lookup_qualified(typ.name, typ, suppress_errors=True)
- return sym is not None and sym.fullname in SELF_TYPE_NAMES
- return False
- def set_original_def(self, previous: Node | None, new: FuncDef | Decorator) -> bool:
- """If 'new' conditionally redefine 'previous', set 'previous' as original
- We reject straight redefinitions of functions, as they are usually
- a programming error. For example:
- def f(): ...
- def f(): ... # Error: 'f' redefined
- """
- if isinstance(new, Decorator):
- new = new.func
- if (
- isinstance(previous, (FuncDef, Decorator))
- and unnamed_function(new.name)
- and unnamed_function(previous.name)
- ):
- return True
- if isinstance(previous, (FuncDef, Var, Decorator)) and new.is_conditional:
- new.original_def = previous
- return True
- else:
- return False
- def update_function_type_variables(self, fun_type: CallableType, defn: FuncItem) -> bool:
- """Make any type variables in the signature of defn explicit.
- Update the signature of defn to contain type variable definitions
- if defn is generic. Return True, if the signature contains typing.Self
- type, or False otherwise.
- """
- with self.tvar_scope_frame(self.tvar_scope.method_frame()):
- a = self.type_analyzer()
- fun_type.variables, has_self_type = a.bind_function_type_variables(fun_type, defn)
- if has_self_type and self.type is not None:
- self.setup_self_type()
- return has_self_type
- def setup_self_type(self) -> None:
- """Setup a (shared) Self type variable for current class.
- We intentionally don't add it to the class symbol table,
- so it can be accessed only by mypy and will not cause
- clashes with user defined names.
- """
- assert self.type is not None
- info = self.type
- if info.self_type is not None:
- if has_placeholder(info.self_type.upper_bound):
- # Similar to regular (user defined) type variables.
- self.process_placeholder(
- None,
- "Self upper bound",
- info,
- force_progress=info.self_type.upper_bound != fill_typevars(info),
- )
- else:
- return
- info.self_type = TypeVarType(
- "Self",
- f"{info.fullname}.Self",
- id=0,
- values=[],
- upper_bound=fill_typevars(info),
- default=AnyType(TypeOfAny.from_omitted_generics),
- )
- def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
- self.statement = defn
- self.add_function_to_symbol_table(defn)
- if not self.recurse_into_functions:
- return
- # NB: Since _visit_overloaded_func_def will call accept on the
- # underlying FuncDefs, the function might get entered twice.
- # This is fine, though, because only the outermost function is
- # used to compute targets.
- with self.scope.function_scope(defn):
- self.analyze_overloaded_func_def(defn)
- def analyze_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
- # OverloadedFuncDef refers to any legitimate situation where you have
- # more than one declaration for the same function in a row. This occurs
- # with a @property with a setter or a deleter, and for a classic
- # @overload.
- defn._fullname = self.qualified_name(defn.name)
- # TODO: avoid modifying items.
- defn.items = defn.unanalyzed_items.copy()
- first_item = defn.items[0]
- first_item.is_overload = True
- first_item.accept(self)
- if isinstance(first_item, Decorator) and first_item.func.is_property:
- # This is a property.
- first_item.func.is_overload = True
- self.analyze_property_with_multi_part_definition(defn)
- typ = function_type(first_item.func, self.named_type("builtins.function"))
- assert isinstance(typ, CallableType)
- types = [typ]
- else:
- # This is an a normal overload. Find the item signatures, the
- # implementation (if outside a stub), and any missing @overload
- # decorators.
- types, impl, non_overload_indexes = self.analyze_overload_sigs_and_impl(defn)
- defn.impl = impl
- if non_overload_indexes:
- self.handle_missing_overload_decorators(
- defn, non_overload_indexes, some_overload_decorators=len(types) > 0
- )
- # If we found an implementation, remove it from the overload item list,
- # as it's special.
- if impl is not None:
- assert impl is defn.items[-1]
- defn.items = defn.items[:-1]
- elif not non_overload_indexes:
- self.handle_missing_overload_implementation(defn)
- if types:
- defn.type = Overloaded(types)
- defn.type.line = defn.line
- if not defn.items:
- # It was not a real overload after all, but function redefinition. We've
- # visited the redefinition(s) already.
- if not defn.impl:
- # For really broken overloads with no items and no implementation we need to keep
- # at least one item to hold basic information like function name.
- defn.impl = defn.unanalyzed_items[-1]
- return
- # We know this is an overload def. Infer properties and perform some checks.
- self.process_final_in_overload(defn)
- self.process_static_or_class_method_in_overload(defn)
- self.process_overload_impl(defn)
- def process_overload_impl(self, defn: OverloadedFuncDef) -> None:
- """Set flags for an overload implementation.
- Currently, this checks for a trivial body in protocols classes,
- where it makes the method implicitly abstract.
- """
- if defn.impl is None:
- return
- impl = defn.impl if isinstance(defn.impl, FuncDef) else defn.impl.func
- if is_trivial_body(impl.body) and self.is_class_scope() and not self.is_stub_file:
- assert self.type is not None
- if self.type.is_protocol:
- impl.abstract_status = IMPLICITLY_ABSTRACT
- if impl.abstract_status != NOT_ABSTRACT:
- impl.is_trivial_body = True
- def analyze_overload_sigs_and_impl(
- self, defn: OverloadedFuncDef
- ) -> tuple[list[CallableType], OverloadPart | None, list[int]]:
- """Find overload signatures, the implementation, and items with missing @overload.
- Assume that the first was already analyzed. As a side effect:
- analyzes remaining items and updates 'is_overload' flags.
- """
- types = []
- non_overload_indexes = []
- impl: OverloadPart | None = None
- for i, item in enumerate(defn.items):
- if i != 0:
- # Assume that the first item was already visited
- item.is_overload = True
- item.accept(self)
- # TODO: support decorated overloaded functions properly
- if isinstance(item, Decorator):
- callable = function_type(item.func, self.named_type("builtins.function"))
- assert isinstance(callable, CallableType)
- if not any(refers_to_fullname(dec, OVERLOAD_NAMES) for dec in item.decorators):
- if i == len(defn.items) - 1 and not self.is_stub_file:
- # Last item outside a stub is impl
- impl = item
- else:
- # Oops it wasn't an overload after all. A clear error
- # will vary based on where in the list it is, record
- # that.
- non_overload_indexes.append(i)
- else:
- item.func.is_overload = True
- types.append(callable)
- if item.var.is_property:
- self.fail("An overload can not be a property", item)
- # If any item was decorated with `@override`, the whole overload
- # becomes an explicit override.
- defn.is_explicit_override |= item.func.is_explicit_override
- elif isinstance(item, FuncDef):
- if i == len(defn.items) - 1 and not self.is_stub_file:
- impl = item
- else:
- non_overload_indexes.append(i)
- return types, impl, non_overload_indexes
- def handle_missing_overload_decorators(
- self,
- defn: OverloadedFuncDef,
- non_overload_indexes: list[int],
- some_overload_decorators: bool,
- ) -> None:
- """Generate errors for overload items without @overload.
- Side effect: remote non-overload items.
- """
- if some_overload_decorators:
- # Some of them were overloads, but not all.
- for idx in non_overload_indexes:
- if self.is_stub_file:
- self.fail(
- "An implementation for an overloaded function "
- "is not allowed in a stub file",
- defn.items[idx],
- )
- else:
- self.fail(
- "The implementation for an overloaded function must come last",
- defn.items[idx],
- )
- else:
- for idx in non_overload_indexes[1:]:
- self.name_already_defined(defn.name, defn.items[idx], defn.items[0])
- if defn.impl:
- self.name_already_defined(defn.name, defn.impl, defn.items[0])
- # Remove the non-overloads
- for idx in reversed(non_overload_indexes):
- del defn.items[idx]
- def handle_missing_overload_implementation(self, defn: OverloadedFuncDef) -> None:
- """Generate error about missing overload implementation (only if needed)."""
- if not self.is_stub_file:
- if self.type and self.type.is_protocol and not self.is_func_scope():
- # An overloaded protocol method doesn't need an implementation,
- # but if it doesn't have one, then it is considered abstract.
- for item in defn.items:
- if isinstance(item, Decorator):
- item.func.abstract_status = IS_ABSTRACT
- else:
- item.abstract_status = IS_ABSTRACT
- else:
- # TODO: also allow omitting an implementation for abstract methods in ABCs?
- self.fail(
- "An overloaded function outside a stub file must have an implementation",
- defn,
- code=codes.NO_OVERLOAD_IMPL,
- )
- def process_final_in_overload(self, defn: OverloadedFuncDef) -> None:
- """Detect the @final status of an overloaded function (and perform checks)."""
- # If the implementation is marked as @final (or the first overload in
- # stubs), then the whole overloaded definition if @final.
- if any(item.is_final for item in defn.items):
- # We anyway mark it as final because it was probably the intention.
- defn.is_final = True
- # Only show the error once per overload
- bad_final = next(ov for ov in defn.items if ov.is_final)
- if not self.is_stub_file:
- self.fail("@final should be applied only to overload implementation", bad_final)
- elif any(item.is_final for item in defn.items[1:]):
- bad_final = next(ov for ov in defn.items[1:] if ov.is_final)
- self.fail(
- "In a stub file @final must be applied only to the first overload", bad_final
- )
- if defn.impl is not None and defn.impl.is_final:
- defn.is_final = True
- def process_static_or_class_method_in_overload(self, defn: OverloadedFuncDef) -> None:
- class_status = []
- static_status = []
- for item in defn.items:
- if isinstance(item, Decorator):
- inner = item.func
- elif isinstance(item, FuncDef):
- inner = item
- else:
- assert False, f"The 'item' variable is an unexpected type: {type(item)}"
- class_status.append(inner.is_class)
- static_status.append(inner.is_static)
- if defn.impl is not None:
- if isinstance(defn.impl, Decorator):
- inner = defn.impl.func
- elif isinstance(defn.impl, FuncDef):
- inner = defn.impl
- else:
- assert False, f"Unexpected impl type: {type(defn.impl)}"
- class_status.append(inner.is_class)
- static_status.append(inner.is_static)
- if len(set(class_status)) != 1:
- self.msg.overload_inconsistently_applies_decorator("classmethod", defn)
- elif len(set(static_status)) != 1:
- self.msg.overload_inconsistently_applies_decorator("staticmethod", defn)
- else:
- defn.is_class = class_status[0]
- defn.is_static = static_status[0]
- def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -> None:
- """Analyze a property defined using multiple methods (e.g., using @x.setter).
- Assume that the first method (@property) has already been analyzed.
- """
- defn.is_property = True
- items = defn.items
- first_item = defn.items[0]
- assert isinstance(first_item, Decorator)
- deleted_items = []
- for i, item in enumerate(items[1:]):
- if isinstance(item, Decorator):
- if len(item.decorators) >= 1:
- node = item.decorators[0]
- if isinstance(node, MemberExpr):
- if node.name == "setter":
- # The first item represents the entire property.
- first_item.var.is_settable_property = True
- # Get abstractness from the original definition.
- item.func.abstract_status = first_item.func.abstract_status
- if node.name == "deleter":
- item.func.abstract_status = first_item.func.abstract_status
- else:
- self.fail(
- f"Only supported top decorator is @{first_item.func.name}.setter", item
- )
- item.func.accept(self)
- else:
- self.fail(f'Unexpected definition for property "{first_item.func.name}"', item)
- deleted_items.append(i + 1)
- for i in reversed(deleted_items):
- del items[i]
- def add_function_to_symbol_table(self, func: FuncDef | OverloadedFuncDef) -> None:
- if self.is_class_scope():
- assert self.type is not None
- func.info = self.type
- func._fullname = self.qualified_name(func.name)
- self.add_symbol(func.name, func, func)
- def analyze_arg_initializers(self, defn: FuncItem) -> None:
- with self.tvar_scope_frame(self.tvar_scope.method_frame()):
- # Analyze default arguments
- for arg in defn.arguments:
- if arg.initializer:
- arg.initializer.accept(self)
- def analyze_function_body(self, defn: FuncItem) -> None:
- is_method = self.is_class_scope()
- with self.tvar_scope_frame(self.tvar_scope.method_frame()):
- # Bind the type variables again to visit the body.
- if defn.type:
- a = self.type_analyzer()
- typ = defn.type
- assert isinstance(typ, CallableType)
- a.bind_function_type_variables(typ, defn)
- for i in range(len(typ.arg_types)):
- store_argument_type(defn, i, typ, self.named_type)
- self.function_stack.append(defn)
- with self.enter(defn):
- for arg in defn.arguments:
- self.add_local(arg.variable, defn)
- # The first argument of a non-static, non-class method is like 'self'
- # (though the name could be different), having the enclosing class's
- # instance type.
- if is_method and (not defn.is_static or defn.name == "__new__") and defn.arguments:
- if not defn.is_class:
- defn.arguments[0].variable.is_self = True
- else:
- defn.arguments[0].variable.is_cls = True
- defn.body.accept(self)
- self.function_stack.pop()
- def check_classvar_in_signature(self, typ: ProperType) -> None:
- t: ProperType
- if isinstance(typ, Overloaded):
- for t in typ.items:
- self.check_classvar_in_signature(t)
- return
- if not isinstance(typ, CallableType):
- return
- for t in get_proper_types(typ.arg_types) + [get_proper_type(typ.ret_type)]:
- if self.is_classvar(t):
- self.fail_invalid_classvar(t)
- # Show only one error per signature
- break
- def check_function_signature(self, fdef: FuncItem) -> None:
- sig = fdef.type
- assert isinstance(sig, CallableType)
- if len(sig.arg_types) < len(fdef.arguments):
- self.fail("Type signature has too few arguments", fdef)
- # Add dummy Any arguments to prevent crashes later.
- num_extra_anys = len(fdef.arguments) - len(sig.arg_types)
- extra_anys = [AnyType(TypeOfAny.from_error)] * num_extra_anys
- sig.arg_types.extend(extra_anys)
- elif len(sig.arg_types) > len(fdef.arguments):
- self.fail("Type signature has too many arguments", fdef, blocker=True)
- def check_paramspec_definition(self, defn: FuncDef) -> None:
- func = defn.type
- assert isinstance(func, CallableType)
- if not any(isinstance(var, ParamSpecType) for var in func.variables):
- return # Function does not have param spec variables
- args = func.var_arg()
- kwargs = func.kw_arg()
- if args is None and kwargs is None:
- return # Looks like this function does not have starred args
- args_defn_type = None
- kwargs_defn_type = None
- for arg_def, arg_kind in zip(defn.arguments, defn.arg_kinds):
- if arg_kind == ARG_STAR:
- args_defn_type = arg_def.type_annotation
- elif arg_kind == ARG_STAR2:
- kwargs_defn_type = arg_def.type_annotation
- # This may happen on invalid `ParamSpec` args / kwargs definition,
- # type analyzer sets types of arguments to `Any`, but keeps
- # definition types as `UnboundType` for now.
- if not (
- (isinstance(args_defn_type, UnboundType) and args_defn_type.name.endswith(".args"))
- or (
- isinstance(kwargs_defn_type, UnboundType)
- and kwargs_defn_type.name.endswith(".kwargs")
- )
- ):
- # Looks like both `*args` and `**kwargs` are not `ParamSpec`
- # It might be something else, skipping.
- return
- args_type = args.typ if args is not None else None
- kwargs_type = kwargs.typ if kwargs is not None else None
- if (
- not isinstance(args_type, ParamSpecType)
- or not isinstance(kwargs_type, ParamSpecType)
- or args_type.name != kwargs_type.name
- ):
- if isinstance(args_defn_type, UnboundType) and args_defn_type.name.endswith(".args"):
- param_name = args_defn_type.name.split(".")[0]
- elif isinstance(kwargs_defn_type, UnboundType) and kwargs_defn_type.name.endswith(
- ".kwargs"
- ):
- param_name = kwargs_defn_type.name.split(".")[0]
- else:
- # Fallback for cases that probably should not ever happen:
- param_name = "P"
- self.fail(
- f'ParamSpec must have "*args" typed as "{param_name}.args" and "**kwargs" typed as "{param_name}.kwargs"',
- func,
- code=codes.VALID_TYPE,
- )
- def visit_decorator(self, dec: Decorator) -> None:
- self.statement = dec
- # TODO: better don't modify them at all.
- dec.decorators = dec.original_decorators.copy()
- dec.func.is_conditional = self.block_depth[-1] > 0
- if not dec.is_overload:
- self.add_symbol(dec.name, dec, dec)
- dec.func._fullname = self.qualified_name(dec.name)
- dec.var._fullname = self.qualified_name(dec.name)
- for d in dec.decorators:
- d.accept(self)
- removed: list[int] = []
- no_type_check = False
- could_be_decorated_property = False
- for i, d in enumerate(dec.decorators):
- # A bunch of decorators are special cased here.
- if refers_to_fullname(d, "abc.abstractmethod"):
- removed.append(i)
- dec.func.abstract_status = IS_ABSTRACT
- self.check_decorated_function_is_method("abstractmethod", dec)
- elif refers_to_fullname(d, ("asyncio.coroutines.coroutine", "types.coroutine")):
- removed.append(i)
- dec.func.is_awaitable_coroutine = True
- elif refers_to_fullname(d, "builtins.staticmethod"):
- removed.append(i)
- dec.func.is_static = True
- dec.var.is_staticmethod = True
- self.check_decorated_function_is_method("staticmethod", dec)
- elif refers_to_fullname(d, "builtins.classmethod"):
- removed.append(i)
- dec.func.is_class = True
- dec.var.is_classmethod = True
- self.check_decorated_function_is_method("classmethod", dec)
- elif refers_to_fullname(d, OVERRIDE_DECORATOR_NAMES):
- removed.append(i)
- dec.func.is_explicit_override = True
- self.check_decorated_function_is_method("override", dec)
- elif refers_to_fullname(
- d,
- (
- "builtins.property",
- "abc.abstractproperty",
- "functools.cached_property",
- "enum.property",
- ),
- ):
- removed.append(i)
- dec.func.is_property = True
- dec.var.is_property = True
- if refers_to_fullname(d, "abc.abstractproperty"):
- dec.func.abstract_status = IS_ABSTRACT
- elif refers_to_fullname(d, "functools.cached_property"):
- dec.var.is_settable_property = True
- self.check_decorated_function_is_method("property", dec)
- elif refers_to_fullname(d, "typing.no_type_check"):
- dec.var.type = AnyType(TypeOfAny.special_form)
- no_type_check = True
- elif refers_to_fullname(d, FINAL_DECORATOR_NAMES):
- if self.is_class_scope():
- assert self.type is not None, "No type set at class scope"
- if self.type.is_protocol:
- self.msg.protocol_members_cant_be_final(d)
- else:
- dec.func.is_final = True
- dec.var.is_final = True
- removed.append(i)
- else:
- self.fail("@final cannot be used with non-method functions", d)
- elif isinstance(d, CallExpr) and refers_to_fullname(
- d.callee, DATACLASS_TRANSFORM_NAMES
- ):
- dec.func.dataclass_transform_spec = self.parse_dataclass_transform_spec(d)
- elif not dec.var.is_property:
- # We have seen a "non-trivial" decorator before seeing @property, if
- # we will see a @property later, give an error, as we don't support this.
- could_be_decorated_property = True
- for i in reversed(removed):
- del dec.decorators[i]
- if (not dec.is_overload or dec.var.is_property) and self.type:
- dec.var.info = self.type
- dec.var.is_initialized_in_class = True
- if not no_type_check and self.recurse_into_functions:
- dec.func.accept(self)
- if could_be_decorated_property and dec.decorators and dec.var.is_property:
- self.fail("Decorators on top of @property are not supported", dec)
- if (dec.func.is_static or dec.func.is_class) and dec.var.is_property:
- self.fail("Only instance methods can be decorated with @property", dec)
- if dec.func.abstract_status == IS_ABSTRACT and dec.func.is_final:
- self.fail(f"Method {dec.func.name} is both abstract and final", dec)
- if dec.func.is_static and dec.func.is_class:
- self.fail(message_registry.CLASS_PATTERN_CLASS_OR_STATIC_METHOD, dec)
- def check_decorated_function_is_method(self, decorator: str, context: Context) -> None:
- if not self.type or self.is_func_scope():
- self.fail(f'"{decorator}" used with a non-method', context)
- #
- # Classes
- #
- def visit_class_def(self, defn: ClassDef) -> None:
- self.statement = defn
- self.incomplete_type_stack.append(not defn.info)
- namespace = self.qualified_name(defn.name)
- with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
- self.analyze_class(defn)
- self.incomplete_type_stack.pop()
- def analyze_class(self, defn: ClassDef) -> None:
- fullname = self.qualified_name(defn.name)
- if not defn.info and not self.is_core_builtin_class(defn):
- # Add placeholder so that self-references in base classes can be
- # resolved. We don't want this to cause a deferral, since if there
- # are no incomplete references, we'll replace this with a TypeInfo
- # before returning.
- placeholder = PlaceholderNode(fullname, defn, defn.line, becomes_typeinfo=True)
- self.add_symbol(defn.name, placeholder, defn, can_defer=False)
- tag = self.track_incomplete_refs()
- # Restore base classes after previous iteration (things like Generic[T] might be removed).
- defn.base_type_exprs.extend(defn.removed_base_type_exprs)
- defn.removed_base_type_exprs.clear()
- self.infer_metaclass_and_bases_from_compat_helpers(defn)
- bases = defn.base_type_exprs
- bases, tvar_defs, is_protocol = self.clean_up_bases_and_infer_type_variables(
- defn, bases, context=defn
- )
- for tvd in tvar_defs:
- if isinstance(tvd, TypeVarType) and any(
- has_placeholder(t) for t in [tvd.upper_bound] + tvd.values
- ):
- # Some type variable bounds or values are not ready, we need
- # to re-analyze this class.
- self.defer()
- if has_placeholder(tvd.default):
- # Placeholder values in TypeVarLikeTypes may get substituted in.
- # Defer current target until they are ready.
- self.mark_incomplete(defn.name, defn)
- return
- self.analyze_class_keywords(defn)
- bases_result = self.analyze_base_classes(bases)
- if bases_result is None or self.found_incomplete_ref(tag):
- # Something was incomplete. Defer current target.
- self.mark_incomplete(defn.name, defn)
- return
- base_types, base_error = bases_result
- if any(isinstance(base, PlaceholderType) for base, _ in base_types):
- # We need to know the TypeInfo of each base to construct the MRO. Placeholder types
- # are okay in nested positions, since they can't affect the MRO.
- self.mark_incomplete(defn.name, defn)
- return
- declared_metaclass, should_defer, any_meta = self.get_declared_metaclass(
- defn.name, defn.metaclass
- )
- if should_defer or self.found_incomplete_ref(tag):
- # Metaclass was not ready. Defer current target.
- self.mark_incomplete(defn.name, defn)
- return
- if self.analyze_typeddict_classdef(defn):
- if defn.info:
- self.setup_type_vars(defn, tvar_defs)
- self.setup_alias_type_vars(defn)
- return
- if self.analyze_namedtuple_classdef(defn, tvar_defs):
- return
- # Create TypeInfo for class now that base classes and the MRO can be calculated.
- self.prepare_class_def(defn)
- self.setup_type_vars(defn, tvar_defs)
- if base_error:
- defn.info.fallback_to_any = True
- if any_meta:
- defn.info.meta_fallback_to_any = True
- with self.scope.class_scope(defn.info):
- self.configure_base_classes(defn, base_types)
- defn.info.is_protocol = is_protocol
- self.recalculate_metaclass(defn, declared_metaclass)
- defn.info.runtime_protocol = False
- for decorator in defn.decorators:
- self.analyze_class_decorator(defn, decorator)
- self.analyze_class_body_common(defn)
- def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None:
- defn.type_vars = tvar_defs
- defn.info.type_vars = []
- # we want to make sure any additional logic in add_type_vars gets run
- defn.info.add_type_vars()
- def setup_alias_type_vars(self, defn: ClassDef) -> None:
- assert defn.info.special_alias is not None
- defn.info.special_alias.alias_tvars = list(defn.type_vars)
- target = defn.info.special_alias.target
- assert isinstance(target, ProperType)
- if isinstance(target, TypedDictType):
- target.fallback.args = tuple(defn.type_vars)
- elif isinstance(target, TupleType):
- target.partial_fallback.args = tuple(defn.type_vars)
- else:
- assert False, f"Unexpected special alias type: {type(target)}"
- def is_core_builtin_class(self, defn: ClassDef) -> bool:
- return self.cur_mod_id == "builtins" and defn.name in CORE_BUILTIN_CLASSES
- def analyze_class_body_common(self, defn: ClassDef) -> None:
- """Parts of class body analysis that are common to all kinds of class defs."""
- self.enter_class(defn.info)
- if any(b.self_type is not None for b in defn.info.mro):
- self.setup_self_type()
- defn.defs.accept(self)
- self.apply_class_plugin_hooks(defn)
- self.leave_class()
- def analyze_typeddict_classdef(self, defn: ClassDef) -> bool:
- if (
- defn.info
- and defn.info.typeddict_type
- and not has_placeholder(defn.info.typeddict_type)
- ):
- # This is a valid TypedDict, and it is fully analyzed.
- return True
- is_typeddict, info = self.typed_dict_analyzer.analyze_typeddict_classdef(defn)
- if is_typeddict:
- for decorator in defn.decorators:
- decorator.accept(self)
- if isinstance(decorator, RefExpr):
- if decorator.fullname in FINAL_DECORATOR_NAMES and info is not None:
- info.is_final = True
- if info is None:
- self.mark_incomplete(defn.name, defn)
- else:
- self.prepare_class_def(defn, info)
- return True
- return False
- def analyze_namedtuple_classdef(
- self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]
- ) -> bool:
- """Check if this class can define a named tuple."""
- if (
- defn.info
- and defn.info.is_named_tuple
- and defn.info.tuple_type
- and not has_placeholder(defn.info.tuple_type)
- ):
- # Don't reprocess everything. We just need to process methods defined
- # in the named tuple class body.
- is_named_tuple = True
- info: TypeInfo | None = defn.info
- else:
- is_named_tuple, info = self.named_tuple_analyzer.analyze_namedtuple_classdef(
- defn, self.is_stub_file, self.is_func_scope()
- )
- if is_named_tuple:
- if info is None:
- self.mark_incomplete(defn.name, defn)
- else:
- self.prepare_class_def(defn, info, custom_names=True)
- self.setup_type_vars(defn, tvar_defs)
- self.setup_alias_type_vars(defn)
- with self.scope.class_scope(defn.info):
- for deco in defn.decorators:
- deco.accept(self)
- if isinstance(deco, RefExpr) and deco.fullname in FINAL_DECORATOR_NAMES:
- info.is_final = True
- with self.named_tuple_analyzer.save_namedtuple_body(info):
- self.analyze_class_body_common(defn)
- return True
- return False
- def apply_class_plugin_hooks(self, defn: ClassDef) -> None:
- """Apply a plugin hook that may infer a more precise definition for a class."""
- for decorator in defn.decorators:
- decorator_name = self.get_fullname_for_hook(decorator)
- if decorator_name:
- hook = self.plugin.get_class_decorator_hook(decorator_name)
- # Special case: if the decorator is itself decorated with
- # typing.dataclass_transform, apply the hook for the dataclasses plugin
- # TODO: remove special casing here
- if hook is None and find_dataclass_transform_spec(decorator):
- hook = dataclasses_plugin.dataclass_tag_callback
- if hook:
- hook(ClassDefContext(defn, decorator, self))
- if defn.metaclass:
- metaclass_name = self.get_fullname_for_hook(defn.metaclass)
- if metaclass_name:
- hook = self.plugin.get_metaclass_hook(metaclass_name)
- if hook:
- hook(ClassDefContext(defn, defn.metaclass, self))
- for base_expr in defn.base_type_exprs:
- base_name = self.get_fullname_for_hook(base_expr)
- if base_name:
- hook = self.plugin.get_base_class_hook(base_name)
- if hook:
- hook(ClassDefContext(defn, base_expr, self))
- # Check if the class definition itself triggers a dataclass transform (via a parent class/
- # metaclass)
- spec = find_dataclass_transform_spec(defn)
- if spec is not None:
- dataclasses_plugin.add_dataclass_tag(defn.info)
- def get_fullname_for_hook(self, expr: Expression) -> str | None:
- if isinstance(expr, CallExpr):
- return self.get_fullname_for_hook(expr.callee)
- elif isinstance(expr, IndexExpr):
- return self.get_fullname_for_hook(expr.base)
- elif isinstance(expr, RefExpr):
- if expr.fullname:
- return expr.fullname
- # If we don't have a fullname look it up. This happens because base classes are
- # analyzed in a different manner (see exprtotype.py) and therefore those AST
- # nodes will not have full names.
- sym = self.lookup_type_node(expr)
- if sym:
- return sym.fullname
- return None
- def analyze_class_keywords(self, defn: ClassDef) -> None:
- for value in defn.keywords.values():
- value.accept(self)
- def enter_class(self, info: TypeInfo) -> None:
- # Remember previous active class
- self.type_stack.append(self.type)
- self.locals.append(None) # Add class scope
- self.is_comprehension_stack.append(False)
- self.block_depth.append(-1) # The class body increments this to 0
- self.loop_depth.append(0)
- self._type = info
- self.missing_names.append(set())
- def leave_class(self) -> None:
- """Restore analyzer state."""
- self.block_depth.pop()
- self.loop_depth.pop()
- self.locals.pop()
- self.is_comprehension_stack.pop()
- self._type = self.type_stack.pop()
- self.missing_names.pop()
- def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
- decorator.accept(self)
- if isinstance(decorator, RefExpr):
- if decorator.fullname in RUNTIME_PROTOCOL_DECOS:
- if defn.info.is_protocol:
- defn.info.runtime_protocol = True
- else:
- self.fail("@runtime_checkable can only be used with protocol classes", defn)
- elif decorator.fullname in FINAL_DECORATOR_NAMES:
- defn.info.is_final = True
- elif isinstance(decorator, CallExpr) and refers_to_fullname(
- decorator.callee, DATACLASS_TRANSFORM_NAMES
- ):
- defn.info.dataclass_transform_spec = self.parse_dataclass_transform_spec(decorator)
- def clean_up_bases_and_infer_type_variables(
- self, defn: ClassDef, base_type_exprs: list[Expression], context: Context
- ) -> tuple[list[Expression], list[TypeVarLikeType], bool]:
- """Remove extra base classes such as Generic and infer type vars.
- For example, consider this class:
- class Foo(Bar, Generic[T]): ...
- Now we will remove Generic[T] from bases of Foo and infer that the
- type variable 'T' is a type argument of Foo.
- Note that this is performed *before* semantic analysis.
- Returns (remaining base expressions, inferred type variables, is protocol).
- """
- removed: list[int] = []
- declared_tvars: TypeVarLikeList = []
- is_protocol = False
- for i, base_expr in enumerate(base_type_exprs):
- if isinstance(base_expr, StarExpr):
- base_expr.valid = True
- self.analyze_type_expr(base_expr)
- try:
- base = self.expr_to_unanalyzed_type(base_expr)
- except TypeTranslationError:
- # This error will be caught later.
- continue
- result = self.analyze_class_typevar_declaration(base)
- if result is not None:
- if declared_tvars:
- self.fail("Only single Generic[...] or Protocol[...] can be in bases", context)
- removed.append(i)
- tvars = result[0]
- is_protocol |= result[1]
- declared_tvars.extend(tvars)
- if isinstance(base, UnboundType):
- sym = self.lookup_qualified(base.name, base)
- if sym is not None and sym.node is not None:
- if sym.node.fullname in PROTOCOL_NAMES and i not in removed:
- # also remove bare 'Protocol' bases
- removed.append(i)
- is_protocol = True
- all_tvars = self.get_all_bases_tvars(base_type_exprs, removed)
- if declared_tvars:
- if len(remove_dups(declared_tvars)) < len(declared_tvars):
- self.fail("Duplicate type variables in Generic[...] or Protocol[...]", context)
- declared_tvars = remove_dups(declared_tvars)
- if not set(all_tvars).issubset(set(declared_tvars)):
- self.fail(
- "If Generic[...] or Protocol[...] is present"
- " it should list all type variables",
- context,
- )
- # In case of error, Generic tvars will go first
- declared_tvars = remove_dups(declared_tvars + all_tvars)
- else:
- declared_tvars = all_tvars
- for i in reversed(removed):
- # We need to actually remove the base class expressions like Generic[T],
- # mostly because otherwise they will create spurious dependencies in fine
- # grained incremental mode.
- defn.removed_base_type_exprs.append(defn.base_type_exprs[i])
- del base_type_exprs[i]
- tvar_defs: list[TypeVarLikeType] = []
- for name, tvar_expr in declared_tvars:
- tvar_def = self.tvar_scope.bind_new(name, tvar_expr)
- tvar_defs.append(tvar_def)
- return base_type_exprs, tvar_defs, is_protocol
- def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList, bool] | None:
- """Analyze type variables declared using Generic[...] or Protocol[...].
- Args:
- base: Non-analyzed base class
- Return None if the base class does not declare type variables. Otherwise,
- return the type variables.
- """
- if not isinstance(base, UnboundType):
- return None
- unbound = base
- sym = self.lookup_qualified(unbound.name, unbound)
- if sym is None or sym.node is None:
- return None
- if (
- sym.node.fullname == "typing.Generic"
- or sym.node.fullname in PROTOCOL_NAMES
- and base.args
- ):
- is_proto = sym.node.fullname != "typing.Generic"
- tvars: TypeVarLikeList = []
- have_type_var_tuple = False
- for arg in unbound.args:
- tag = self.track_incomplete_refs()
- tvar = self.analyze_unbound_tvar(arg)
- if tvar:
- if isinstance(tvar[1], TypeVarTupleExpr):
- if have_type_var_tuple:
- self.fail("Can only use one type var tuple in a class def", base)
- continue
- have_type_var_tuple = True
- tvars.append(tvar)
- elif not self.found_incomplete_ref(tag):
- self.fail("Free type variable expected in %s[...]" % sym.node.name, base)
- return tvars, is_proto
- return None
- def analyze_unbound_tvar(self, t: Type) -> tuple[str, TypeVarLikeExpr] | None:
- if not isinstance(t, UnboundType):
- return None
- unbound = t
- sym = self.lookup_qualified(unbound.name, unbound)
- if sym and isinstance(sym.node, PlaceholderNode):
- self.record_incomplete_ref()
- if sym and isinstance(sym.node, ParamSpecExpr):
- if sym.fullname and not self.tvar_scope.allow_binding(sym.fullname):
- # It's bound by our type variable scope
- return None
- return unbound.name, sym.node
- if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"):
- inner_t = unbound.args[0]
- if not isinstance(inner_t, UnboundType):
- return None
- inner_unbound = inner_t
- inner_sym = self.lookup_qualified(inner_unbound.name, inner_unbound)
- if inner_sym and isinstance(inner_sym.node, PlaceholderNode):
- self.record_incomplete_ref()
- if inner_sym and isinstance(inner_sym.node, TypeVarTupleExpr):
- if inner_sym.fullname and not self.tvar_scope.allow_binding(inner_sym.fullname):
- # It's bound by our type variable scope
- return None
- return inner_unbound.name, inner_sym.node
- if sym is None or not isinstance(sym.node, TypeVarExpr):
- return None
- elif sym.fullname and not self.tvar_scope.allow_binding(sym.fullname):
- # It's bound by our type variable scope
- return None
- else:
- assert isinstance(sym.node, TypeVarExpr)
- return unbound.name, sym.node
- def get_all_bases_tvars(
- self, base_type_exprs: list[Expression], removed: list[int]
- ) -> TypeVarLikeList:
- """Return all type variable references in bases."""
- tvars: TypeVarLikeList = []
- for i, base_expr in enumerate(base_type_exprs):
- if i not in removed:
- try:
- base = self.expr_to_unanalyzed_type(base_expr)
- except TypeTranslationError:
- # This error will be caught later.
- continue
- base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope))
- tvars.extend(base_tvars)
- return remove_dups(tvars)
- def get_and_bind_all_tvars(self, type_exprs: list[Expression]) -> list[TypeVarLikeType]:
- """Return all type variable references in item type expressions.
- This is a helper for generic TypedDicts and NamedTuples. Essentially it is
- a simplified version of the logic we use for ClassDef bases. We duplicate
- some amount of code, because it is hard to refactor common pieces.
- """
- tvars = []
- for base_expr in type_exprs:
- try:
- base = self.expr_to_unanalyzed_type(base_expr)
- except TypeTranslationError:
- # This error will be caught later.
- continue
- base_tvars = base.accept(TypeVarLikeQuery(self, self.tvar_scope))
- tvars.extend(base_tvars)
- tvars = remove_dups(tvars) # Variables are defined in order of textual appearance.
- tvar_defs = []
- for name, tvar_expr in tvars:
- tvar_def = self.tvar_scope.bind_new(name, tvar_expr)
- tvar_defs.append(tvar_def)
- return tvar_defs
- def prepare_class_def(
- self, defn: ClassDef, info: TypeInfo | None = None, custom_names: bool = False
- ) -> None:
- """Prepare for the analysis of a class definition.
- Create an empty TypeInfo and store it in a symbol table, or if the 'info'
- argument is provided, store it instead (used for magic type definitions).
- """
- if not defn.info:
- defn.fullname = self.qualified_name(defn.name)
- # TODO: Nested classes
- info = info or self.make_empty_type_info(defn)
- defn.info = info
- info.defn = defn
- if not custom_names:
- # Some special classes (in particular NamedTuples) use custom fullname logic.
- # Don't override it here (also see comment below, this needs cleanup).
- if not self.is_func_scope():
- info._fullname = self.qualified_name(defn.name)
- else:
- info._fullname = info.name
- local_name = defn.name
- if "@" in local_name:
- local_name = local_name.split("@")[0]
- self.add_symbol(local_name, defn.info, defn)
- if self.is_nested_within_func_scope():
- # We need to preserve local classes, let's store them
- # in globals under mangled unique names
- #
- # TODO: Putting local classes into globals breaks assumptions in fine-grained
- # incremental mode and we should avoid it. In general, this logic is too
- # ad-hoc and needs to be removed/refactored.
- if "@" not in defn.info._fullname:
- global_name = defn.info.name + "@" + str(defn.line)
- defn.info._fullname = self.cur_mod_id + "." + global_name
- else:
- # Preserve name from previous fine-grained incremental run.
- global_name = defn.info.name
- defn.fullname = defn.info._fullname
- if defn.info.is_named_tuple:
- # Named tuple nested within a class is stored in the class symbol table.
- self.add_symbol_skip_local(global_name, defn.info)
- else:
- self.globals[global_name] = SymbolTableNode(GDEF, defn.info)
- def make_empty_type_info(self, defn: ClassDef) -> TypeInfo:
- if (
- self.is_module_scope()
- and self.cur_mod_id == "builtins"
- and defn.name in CORE_BUILTIN_CLASSES
- ):
- # Special case core built-in classes. A TypeInfo was already
- # created for it before semantic analysis, but with a dummy
- # ClassDef. Patch the real ClassDef object.
- info = self.globals[defn.name].node
- assert isinstance(info, TypeInfo)
- else:
- info = TypeInfo(SymbolTable(), defn, self.cur_mod_id)
- info.set_line(defn)
- return info
- def get_name_repr_of_expr(self, expr: Expression) -> str | None:
- """Try finding a short simplified textual representation of a base class expression."""
- if isinstance(expr, NameExpr):
- return expr.name
- if isinstance(expr, MemberExpr):
- return get_member_expr_fullname(expr)
- if isinstance(expr, IndexExpr):
- return self.get_name_repr_of_expr(expr.base)
- if isinstance(expr, CallExpr):
- return self.get_name_repr_of_expr(expr.callee)
- return None
- def analyze_base_classes(
- self, base_type_exprs: list[Expression]
- ) -> tuple[list[tuple[ProperType, Expression]], bool] | None:
- """Analyze base class types.
- Return None if some definition was incomplete. Otherwise, return a tuple
- with these items:
- * List of (analyzed type, original expression) tuples
- * Boolean indicating whether one of the bases had a semantic analysis error
- """
- is_error = False
- bases = []
- for base_expr in base_type_exprs:
- if (
- isinstance(base_expr, RefExpr)
- and base_expr.fullname in TYPED_NAMEDTUPLE_NAMES + TPDICT_NAMES
- ):
- # Ignore magic bases for now.
- continue
- try:
- base = self.expr_to_analyzed_type(
- base_expr, allow_placeholder=True, allow_type_any=True
- )
- except TypeTranslationError:
- name = self.get_name_repr_of_expr(base_expr)
- if isinstance(base_expr, CallExpr):
- msg = "Unsupported dynamic base class"
- else:
- msg = "Invalid base class"
- if name:
- msg += f' "{name}"'
- self.fail(msg, base_expr)
- is_error = True
- continue
- if base is None:
- return None
- base = get_proper_type(base)
- bases.append((base, base_expr))
- return bases, is_error
- def configure_base_classes(
- self, defn: ClassDef, bases: list[tuple[ProperType, Expression]]
- ) -> None:
- """Set up base classes.
- This computes several attributes on the corresponding TypeInfo defn.info
- related to the base classes: defn.info.bases, defn.info.mro, and
- miscellaneous others (at least tuple_type, fallback_to_any, and is_enum.)
- """
- base_types: list[Instance] = []
- info = defn.info
- for base, base_expr in bases:
- if isinstance(base, TupleType):
- actual_base = self.configure_tuple_base_class(defn, base)
- base_types.append(actual_base)
- elif isinstance(base, Instance):
- if base.type.is_newtype:
- self.fail('Cannot subclass "NewType"', defn)
- base_types.append(base)
- elif isinstance(base, AnyType):
- if self.options.disallow_subclassing_any:
- if isinstance(base_expr, (NameExpr, MemberExpr)):
- msg = f'Class cannot subclass "{base_expr.name}" (has type "Any")'
- else:
- msg = 'Class cannot subclass value of type "Any"'
- self.fail(msg, base_expr)
- info.fallback_to_any = True
- elif isinstance(base, TypedDictType):
- base_types.append(base.fallback)
- else:
- msg = "Invalid base class"
- name = self.get_name_repr_of_expr(base_expr)
- if name:
- msg += f' "{name}"'
- self.fail(msg, base_expr)
- info.fallback_to_any = True
- if self.options.disallow_any_unimported and has_any_from_unimported_type(base):
- if isinstance(base_expr, (NameExpr, MemberExpr)):
- prefix = f"Base type {base_expr.name}"
- else:
- prefix = "Base type"
- self.msg.unimported_type_becomes_any(prefix, base, base_expr)
- check_for_explicit_any(
- base, self.options, self.is_typeshed_stub_file, self.msg, context=base_expr
- )
- # Add 'object' as implicit base if there is no other base class.
- if not base_types and defn.fullname != "builtins.object":
- base_types.append(self.object_type())
- info.bases = base_types
- # Calculate the MRO.
- if not self.verify_base_classes(defn):
- self.set_dummy_mro(defn.info)
- return
- if not self.verify_duplicate_base_classes(defn):
- # We don't want to block the typechecking process,
- # so, we just insert `Any` as the base class and show an error.
- self.set_any_mro(defn.info)
- self.calculate_class_mro(defn, self.object_type)
- def configure_tuple_base_class(self, defn: ClassDef, base: TupleType) -> Instance:
- info = defn.info
- # There may be an existing valid tuple type from previous semanal iterations.
- # Use equality to check if it is the case.
- if info.tuple_type and info.tuple_type != base and not has_placeholder(info.tuple_type):
- self.fail("Class has two incompatible bases derived from tuple", defn)
- defn.has_incompatible_baseclass = True
- if info.special_alias and has_placeholder(info.special_alias.target):
- self.process_placeholder(
- None, "tuple base", defn, force_progress=base != info.tuple_type
- )
- info.update_tuple_type(base)
- self.setup_alias_type_vars(defn)
- if base.partial_fallback.type.fullname == "builtins.tuple" and not has_placeholder(base):
- # Fallback can only be safely calculated after semantic analysis, since base
- # classes may be incomplete. Postpone the calculation.
- self.schedule_patch(PRIORITY_FALLBACKS, lambda: calculate_tuple_fallback(base))
- return base.partial_fallback
- def set_dummy_mro(self, info: TypeInfo) -> None:
- # Give it an MRO consisting of just the class itself and object.
- info.mro = [info, self.object_type().type]
- info.bad_mro = True
- def set_any_mro(self, info: TypeInfo) -> None:
- # Give it an MRO consisting direct `Any` subclass.
- info.fallback_to_any = True
- info.mro = [info, self.object_type().type]
- def calculate_class_mro(
- self, defn: ClassDef, obj_type: Callable[[], Instance] | None = None
- ) -> None:
- """Calculate method resolution order for a class.
- `obj_type` exists just to fill in empty base class list in case of an error.
- """
- try:
- calculate_mro(defn.info, obj_type)
- except MroError:
- self.fail(
- "Cannot determine consistent method resolution "
- 'order (MRO) for "%s"' % defn.name,
- defn,
- )
- self.set_dummy_mro(defn.info)
- # Allow plugins to alter the MRO to handle the fact that `def mro()`
- # on metaclasses permits MRO rewriting.
- if defn.fullname:
- hook = self.plugin.get_customize_class_mro_hook(defn.fullname)
- if hook:
- hook(ClassDefContext(defn, FakeExpression(), self))
- def infer_metaclass_and_bases_from_compat_helpers(self, defn: ClassDef) -> None:
- """Lookup for special metaclass declarations, and update defn fields accordingly.
- * six.with_metaclass(M, B1, B2, ...)
- * @six.add_metaclass(M)
- * future.utils.with_metaclass(M, B1, B2, ...)
- * past.utils.with_metaclass(M, B1, B2, ...)
- """
- # Look for six.with_metaclass(M, B1, B2, ...)
- with_meta_expr: Expression | None = None
- if len(defn.base_type_exprs) == 1:
- base_expr = defn.base_type_exprs[0]
- if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr):
- self.analyze_type_expr(base_expr)
- if (
- base_expr.callee.fullname
- in {
- "six.with_metaclass",
- "future.utils.with_metaclass",
- "past.utils.with_metaclass",
- }
- and len(base_expr.args) >= 1
- and all(kind == ARG_POS for kind in base_expr.arg_kinds)
- ):
- with_meta_expr = base_expr.args[0]
- defn.base_type_exprs = base_expr.args[1:]
- # Look for @six.add_metaclass(M)
- add_meta_expr: Expression | None = None
- for dec_expr in defn.decorators:
- if isinstance(dec_expr, CallExpr) and isinstance(dec_expr.callee, RefExpr):
- dec_expr.callee.accept(self)
- if (
- dec_expr.callee.fullname == "six.add_metaclass"
- and len(dec_expr.args) == 1
- and dec_expr.arg_kinds[0] == ARG_POS
- ):
- add_meta_expr = dec_expr.args[0]
- break
- metas = {defn.metaclass, with_meta_expr, add_meta_expr} - {None}
- if len(metas) == 0:
- return
- if len(metas) > 1:
- self.fail("Multiple metaclass definitions", defn)
- return
- defn.metaclass = metas.pop()
- def verify_base_classes(self, defn: ClassDef) -> bool:
- info = defn.info
- cycle = False
- for base in info.bases:
- baseinfo = base.type
- if self.is_base_class(info, baseinfo):
- self.fail("Cycle in inheritance hierarchy", defn)
- cycle = True
- return not cycle
- def verify_duplicate_base_classes(self, defn: ClassDef) -> bool:
- dup = find_duplicate(defn.info.direct_base_classes())
- if dup:
- self.fail(f'Duplicate base class "{dup.name}"', defn)
- return not dup
- def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
- """Determine if t is a base class of s (but do not use mro)."""
- # Search the base class graph for t, starting from s.
- worklist = [s]
- visited = {s}
- while worklist:
- nxt = worklist.pop()
- if nxt == t:
- return True
- for base in nxt.bases:
- if base.type not in visited:
- worklist.append(base.type)
- visited.add(base.type)
- return False
- def get_declared_metaclass(
- self, name: str, metaclass_expr: Expression | None
- ) -> tuple[Instance | None, bool, bool]:
- """Get declared metaclass from metaclass expression.
- Returns a tuple of three values:
- * A metaclass instance or None
- * A boolean indicating whether we should defer
- * A boolean indicating whether we should set metaclass Any fallback
- (either for Any metaclass or invalid/dynamic metaclass).
- The two boolean flags can only be True if instance is None.
- """
- declared_metaclass = None
- if metaclass_expr:
- metaclass_name = None
- if isinstance(metaclass_expr, NameExpr):
- metaclass_name = metaclass_expr.name
- elif isinstance(metaclass_expr, MemberExpr):
- metaclass_name = get_member_expr_fullname(metaclass_expr)
- if metaclass_name is None:
- self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr)
- return None, False, True
- sym = self.lookup_qualified(metaclass_name, metaclass_expr)
- if sym is None:
- # Probably a name error - it is already handled elsewhere
- return None, False, True
- if isinstance(sym.node, Var) and isinstance(get_proper_type(sym.node.type), AnyType):
- if self.options.disallow_subclassing_any:
- self.fail(
- f'Class cannot use "{sym.node.name}" as a metaclass (has type "Any")',
- metaclass_expr,
- )
- return None, False, True
- if isinstance(sym.node, PlaceholderNode):
- return None, True, False # defer later in the caller
- # Support type aliases, like `_Meta: TypeAlias = type`
- if (
- isinstance(sym.node, TypeAlias)
- and sym.node.no_args
- and isinstance(sym.node.target, ProperType)
- and isinstance(sym.node.target, Instance)
- ):
- metaclass_info: Node | None = sym.node.target.type
- else:
- metaclass_info = sym.node
- if not isinstance(metaclass_info, TypeInfo) or metaclass_info.tuple_type is not None:
- self.fail(f'Invalid metaclass "{metaclass_name}"', metaclass_expr)
- return None, False, False
- if not metaclass_info.is_metaclass():
- self.fail(
- 'Metaclasses not inheriting from "type" are not supported', metaclass_expr
- )
- return None, False, False
- inst = fill_typevars(metaclass_info)
- assert isinstance(inst, Instance)
- declared_metaclass = inst
- return declared_metaclass, False, False
- def recalculate_metaclass(self, defn: ClassDef, declared_metaclass: Instance | None) -> None:
- defn.info.declared_metaclass = declared_metaclass
- defn.info.metaclass_type = defn.info.calculate_metaclass_type()
- if any(info.is_protocol for info in defn.info.mro):
- if (
- not defn.info.metaclass_type
- or defn.info.metaclass_type.type.fullname == "builtins.type"
- ):
- # All protocols and their subclasses have ABCMeta metaclass by default.
- # TODO: add a metaclass conflict check if there is another metaclass.
- abc_meta = self.named_type_or_none("abc.ABCMeta", [])
- if abc_meta is not None: # May be None in tests with incomplete lib-stub.
- defn.info.metaclass_type = abc_meta
- if defn.info.metaclass_type and defn.info.metaclass_type.type.has_base("enum.EnumMeta"):
- defn.info.is_enum = True
- if defn.type_vars:
- self.fail("Enum class cannot be generic", defn)
- #
- # Imports
- #
- def visit_import(self, i: Import) -> None:
- self.statement = i
- for id, as_id in i.ids:
- # Modules imported in a stub file without using 'import X as X' won't get exported
- # When implicit re-exporting is disabled, we have the same behavior as stubs.
- use_implicit_reexport = not self.is_stub_file and self.options.implicit_reexport
- if as_id is not None:
- base_id = id
- imported_id = as_id
- module_public = use_implicit_reexport or id.split(".")[-1] == as_id
- else:
- base_id = id.split(".")[0]
- imported_id = base_id
- module_public = use_implicit_reexport
- if base_id in self.modules:
- node = self.modules[base_id]
- if self.is_func_scope():
- kind = LDEF
- elif self.type is not None:
- kind = MDEF
- else:
- kind = GDEF
- symbol = SymbolTableNode(
- kind, node, module_public=module_public, module_hidden=not module_public
- )
- self.add_imported_symbol(
- imported_id,
- symbol,
- context=i,
- module_public=module_public,
- module_hidden=not module_public,
- )
- else:
- self.add_unknown_imported_symbol(
- imported_id,
- context=i,
- target_name=base_id,
- module_public=module_public,
- module_hidden=not module_public,
- )
- def visit_import_from(self, imp: ImportFrom) -> None:
- self.statement = imp
- module_id = self.correct_relative_import(imp)
- module = self.modules.get(module_id)
- for id, as_id in imp.names:
- fullname = module_id + "." + id
- self.set_future_import_flags(fullname)
- if module is None:
- node = None
- elif module_id == self.cur_mod_id and fullname in self.modules:
- # Submodule takes precedence over definition in surround package, for
- # compatibility with runtime semantics in typical use cases. This
- # could more precisely model runtime semantics by taking into account
- # the line number beyond which the local definition should take
- # precedence, but doesn't seem to be important in most use cases.
- node = SymbolTableNode(GDEF, self.modules[fullname])
- else:
- if id == as_id == "__all__" and module_id in self.export_map:
- self.all_exports[:] = self.export_map[module_id]
- node = module.names.get(id)
- missing_submodule = False
- imported_id = as_id or id
- # Modules imported in a stub file without using 'from Y import X as X' will
- # not get exported.
- # When implicit re-exporting is disabled, we have the same behavior as stubs.
- use_implicit_reexport = not self.is_stub_file and self.options.implicit_reexport
- module_public = use_implicit_reexport or (as_id is not None and id == as_id)
- # If the module does not contain a symbol with the name 'id',
- # try checking if it's a module instead.
- if not node:
- mod = self.modules.get(fullname)
- if mod is not None:
- kind = self.current_symbol_kind()
- node = SymbolTableNode(kind, mod)
- elif fullname in self.missing_modules:
- missing_submodule = True
- # If it is still not resolved, check for a module level __getattr__
- if (
- module
- and not node
- and (module.is_stub or self.options.python_version >= (3, 7))
- and "__getattr__" in module.names
- ):
- # We store the fullname of the original definition so that we can
- # detect whether two imported names refer to the same thing.
- fullname = module_id + "." + id
- gvar = self.create_getattr_var(module.names["__getattr__"], imported_id, fullname)
- if gvar:
- self.add_symbol(
- imported_id,
- gvar,
- imp,
- module_public=module_public,
- module_hidden=not module_public,
- )
- continue
- if node:
- self.process_imported_symbol(
- node, module_id, id, imported_id, fullname, module_public, context=imp
- )
- if node.module_hidden:
- self.report_missing_module_attribute(
- module_id,
- id,
- imported_id,
- module_public=module_public,
- module_hidden=not module_public,
- context=imp,
- add_unknown_imported_symbol=False,
- )
- elif module and not missing_submodule:
- # Target module exists but the imported name is missing or hidden.
- self.report_missing_module_attribute(
- module_id,
- id,
- imported_id,
- module_public=module_public,
- module_hidden=not module_public,
- context=imp,
- )
- else:
- # Import of a missing (sub)module.
- self.add_unknown_imported_symbol(
- imported_id,
- imp,
- target_name=fullname,
- module_public=module_public,
- module_hidden=not module_public,
- )
- def process_imported_symbol(
- self,
- node: SymbolTableNode,
- module_id: str,
- id: str,
- imported_id: str,
- fullname: str,
- module_public: bool,
- context: ImportBase,
- ) -> None:
- module_hidden = not module_public and (
- # `from package import submodule` should work regardless of whether package
- # re-exports submodule, so we shouldn't hide it
- not isinstance(node.node, MypyFile)
- or fullname not in self.modules
- # but given `from somewhere import random_unrelated_module` we should hide
- # random_unrelated_module
- or not fullname.startswith(self.cur_mod_id + ".")
- )
- if isinstance(node.node, PlaceholderNode):
- if self.final_iteration:
- self.report_missing_module_attribute(
- module_id,
- id,
- imported_id,
- module_public=module_public,
- module_hidden=module_hidden,
- context=context,
- )
- return
- else:
- # This might become a type.
- self.mark_incomplete(
- imported_id,
- node.node,
- module_public=module_public,
- module_hidden=module_hidden,
- becomes_typeinfo=True,
- )
- # NOTE: we take the original node even for final `Var`s. This is to support
- # a common pattern when constants are re-exported (same applies to import *).
- self.add_imported_symbol(
- imported_id, node, context, module_public=module_public, module_hidden=module_hidden
- )
- def report_missing_module_attribute(
- self,
- import_id: str,
- source_id: str,
- imported_id: str,
- module_public: bool,
- module_hidden: bool,
- context: Node,
- add_unknown_imported_symbol: bool = True,
- ) -> None:
- # Missing attribute.
- if self.is_incomplete_namespace(import_id):
- # We don't know whether the name will be there, since the namespace
- # is incomplete. Defer the current target.
- self.mark_incomplete(
- imported_id, context, module_public=module_public, module_hidden=module_hidden
- )
- return
- message = f'Module "{import_id}" has no attribute "{source_id}"'
- # Suggest alternatives, if any match is found.
- module = self.modules.get(import_id)
- if module:
- if source_id in module.names.keys() and not module.names[source_id].module_public:
- message = (
- f'Module "{import_id}" does not explicitly export attribute "{source_id}"'
- )
- else:
- alternatives = set(module.names.keys()).difference({source_id})
- matches = best_matches(source_id, alternatives, n=3)
- if matches:
- suggestion = f"; maybe {pretty_seq(matches, 'or')}?"
- message += f"{suggestion}"
- self.fail(message, context, code=codes.ATTR_DEFINED)
- if add_unknown_imported_symbol:
- self.add_unknown_imported_symbol(
- imported_id,
- context,
- target_name=None,
- module_public=module_public,
- module_hidden=not module_public,
- )
- if import_id == "typing":
- # The user probably has a missing definition in a test fixture. Let's verify.
- fullname = f"builtins.{source_id.lower()}"
- if (
- self.lookup_fully_qualified_or_none(fullname) is None
- and fullname in SUGGESTED_TEST_FIXTURES
- ):
- # Yes. Generate a helpful note.
- self.msg.add_fixture_note(fullname, context)
- else:
- typing_extensions = self.modules.get("typing_extensions")
- if typing_extensions and source_id in typing_extensions.names:
- self.msg.note(
- f"Use `from typing_extensions import {source_id}` instead",
- context,
- code=codes.ATTR_DEFINED,
- )
- self.msg.note(
- "See https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module",
- context,
- code=codes.ATTR_DEFINED,
- )
- def process_import_over_existing_name(
- self,
- imported_id: str,
- existing_symbol: SymbolTableNode,
- module_symbol: SymbolTableNode,
- import_node: ImportBase,
- ) -> bool:
- if existing_symbol.node is module_symbol.node:
- # We added this symbol on previous iteration.
- return False
- if existing_symbol.kind in (LDEF, GDEF, MDEF) and isinstance(
- existing_symbol.node, (Var, FuncDef, TypeInfo, Decorator, TypeAlias)
- ):
- # This is a valid import over an existing definition in the file. Construct a dummy
- # assignment that we'll use to type check the import.
- lvalue = NameExpr(imported_id)
- lvalue.kind = existing_symbol.kind
- lvalue.node = existing_symbol.node
- rvalue = NameExpr(imported_id)
- rvalue.kind = module_symbol.kind
- rvalue.node = module_symbol.node
- if isinstance(rvalue.node, TypeAlias):
- # Suppress bogus errors from the dummy assignment if rvalue is an alias.
- # Otherwise mypy may complain that alias is invalid in runtime context.
- rvalue.is_alias_rvalue = True
- assignment = AssignmentStmt([lvalue], rvalue)
- for node in assignment, lvalue, rvalue:
- node.set_line(import_node)
- import_node.assignments.append(assignment)
- return True
- return False
- def correct_relative_import(self, node: ImportFrom | ImportAll) -> str:
- import_id, ok = correct_relative_import(
- self.cur_mod_id, node.relative, node.id, self.cur_mod_node.is_package_init_file()
- )
- if not ok:
- self.fail("Relative import climbs too many namespaces", node)
- return import_id
- def visit_import_all(self, i: ImportAll) -> None:
- i_id = self.correct_relative_import(i)
- if i_id in self.modules:
- m = self.modules[i_id]
- if self.is_incomplete_namespace(i_id):
- # Any names could be missing from the current namespace if the target module
- # namespace is incomplete.
- self.mark_incomplete("*", i)
- for name, node in m.names.items():
- fullname = i_id + "." + name
- self.set_future_import_flags(fullname)
- if node is None:
- continue
- # if '__all__' exists, all nodes not included have had module_public set to
- # False, and we can skip checking '_' because it's been explicitly included.
- if node.module_public and (not name.startswith("_") or "__all__" in m.names):
- if isinstance(node.node, MypyFile):
- # Star import of submodule from a package, add it as a dependency.
- self.imports.add(node.node.fullname)
- # `from x import *` always reexports symbols
- self.add_imported_symbol(
- name, node, context=i, module_public=True, module_hidden=False
- )
- else:
- # Don't add any dummy symbols for 'from x import *' if 'x' is unknown.
- pass
- #
- # Assignment
- #
- def visit_assignment_expr(self, s: AssignmentExpr) -> None:
- s.value.accept(self)
- if self.is_func_scope():
- if not self.check_valid_comprehension(s):
- return
- self.analyze_lvalue(s.target, escape_comprehensions=True, has_explicit_value=True)
- def check_valid_comprehension(self, s: AssignmentExpr) -> bool:
- """Check that assignment expression is not nested within comprehension at class scope.
- class C:
- [(j := i) for i in [1, 2, 3]]
- is a syntax error that is not enforced by Python parser, but at later steps.
- """
- for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)):
- if not is_comprehension and i < len(self.locals) - 1:
- if self.locals[-1 - i] is None:
- self.fail(
- "Assignment expression within a comprehension"
- " cannot be used in a class body",
- s,
- code=codes.SYNTAX,
- serious=True,
- blocker=True,
- )
- return False
- break
- return True
- def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
- self.statement = s
- # Special case assignment like X = X.
- if self.analyze_identity_global_assignment(s):
- return
- tag = self.track_incomplete_refs()
- # Here we have a chicken and egg problem: at this stage we can't call
- # can_be_type_alias(), because we have not enough information about rvalue.
- # But we can't use a full visit because it may emit extra incomplete refs (namely
- # when analysing any type applications there) thus preventing the further analysis.
- # To break the tie, we first analyse rvalue partially, if it can be a type alias.
- if self.can_possibly_be_type_form(s):
- old_basic_type_applications = self.basic_type_applications
- self.basic_type_applications = True
- with self.allow_unbound_tvars_set():
- s.rvalue.accept(self)
- self.basic_type_applications = old_basic_type_applications
- else:
- s.rvalue.accept(self)
- if self.found_incomplete_ref(tag) or self.should_wait_rhs(s.rvalue):
- # Initializer couldn't be fully analyzed. Defer the current node and give up.
- # Make sure that if we skip the definition of some local names, they can't be
- # added later in this scope, since an earlier definition should take precedence.
- for expr in names_modified_by_assignment(s):
- self.mark_incomplete(expr.name, expr)
- return
- if self.can_possibly_be_type_form(s):
- # Now re-visit those rvalues that were we skipped type applications above.
- # This should be safe as generally semantic analyzer is idempotent.
- with self.allow_unbound_tvars_set():
- s.rvalue.accept(self)
- # The r.h.s. is now ready to be classified, first check if it is a special form:
- special_form = False
- # * type alias
- if self.check_and_set_up_type_alias(s):
- s.is_alias_def = True
- special_form = True
- # * type variable definition
- elif self.process_typevar_declaration(s):
- special_form = True
- elif self.process_paramspec_declaration(s):
- special_form = True
- elif self.process_typevartuple_declaration(s):
- special_form = True
- # * type constructors
- elif self.analyze_namedtuple_assign(s):
- special_form = True
- elif self.analyze_typeddict_assign(s):
- special_form = True
- elif self.newtype_analyzer.process_newtype_declaration(s):
- special_form = True
- elif self.analyze_enum_assign(s):
- special_form = True
- if special_form:
- self.record_special_form_lvalue(s)
- return
- # Clear the alias flag if assignment turns out not a special form after all. It
- # may be set to True while there were still placeholders due to forward refs.
- s.is_alias_def = False
- # OK, this is a regular assignment, perform the necessary analysis steps.
- s.is_final_def = self.unwrap_final(s)
- self.analyze_lvalues(s)
- self.check_final_implicit_def(s)
- self.store_final_status(s)
- self.check_classvar(s)
- self.process_type_annotation(s)
- self.apply_dynamic_class_hook(s)
- if not s.type:
- self.process_module_assignment(s.lvalues, s.rvalue, s)
- self.process__all__(s)
- self.process__deletable__(s)
- self.process__slots__(s)
- def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool:
- """Special case 'X = X' in global scope.
- This allows supporting some important use cases.
- Return true if special casing was applied.
- """
- if not isinstance(s.rvalue, NameExpr) or len(s.lvalues) != 1:
- # Not of form 'X = X'
- return False
- lvalue = s.lvalues[0]
- if not isinstance(lvalue, NameExpr) or s.rvalue.name != lvalue.name:
- # Not of form 'X = X'
- return False
- if self.type is not None or self.is_func_scope():
- # Not in global scope
- return False
- # It's an assignment like 'X = X' in the global scope.
- name = lvalue.name
- sym = self.lookup(name, s)
- if sym is None:
- if self.final_iteration:
- # Fall back to normal assignment analysis.
- return False
- else:
- self.defer()
- return True
- else:
- if sym.node is None:
- # Something special -- fall back to normal assignment analysis.
- return False
- if name not in self.globals:
- # The name is from builtins. Add an alias to the current module.
- self.add_symbol(name, sym.node, s)
- if not isinstance(sym.node, PlaceholderNode):
- for node in s.rvalue, lvalue:
- node.node = sym.node
- node.kind = GDEF
- node.fullname = sym.node.fullname
- return True
- def should_wait_rhs(self, rv: Expression) -> bool:
- """Can we already classify this r.h.s. of an assignment or should we wait?
- This returns True if we don't have enough information to decide whether
- an assignment is just a normal variable definition or a special form.
- Always return False if this is a final iteration. This will typically cause
- the lvalue to be classified as a variable plus emit an error.
- """
- if self.final_iteration:
- # No chance, nothing has changed.
- return False
- if isinstance(rv, NameExpr):
- n = self.lookup(rv.name, rv)
- if n and isinstance(n.node, PlaceholderNode) and not n.node.becomes_typeinfo:
- return True
- elif isinstance(rv, MemberExpr):
- fname = get_member_expr_fullname(rv)
- if fname:
- n = self.lookup_qualified(fname, rv, suppress_errors=True)
- if n and isinstance(n.node, PlaceholderNode) and not n.node.becomes_typeinfo:
- return True
- elif isinstance(rv, IndexExpr) and isinstance(rv.base, RefExpr):
- return self.should_wait_rhs(rv.base)
- elif isinstance(rv, CallExpr) and isinstance(rv.callee, RefExpr):
- # This is only relevant for builtin SCC where things like 'TypeVar'
- # may be not ready.
- return self.should_wait_rhs(rv.callee)
- return False
- def can_be_type_alias(self, rv: Expression, allow_none: bool = False) -> bool:
- """Is this a valid r.h.s. for an alias definition?
- Note: this function should be only called for expressions where self.should_wait_rhs()
- returns False.
- """
- if isinstance(rv, RefExpr) and self.is_type_ref(rv, bare=True):
- return True
- if isinstance(rv, IndexExpr) and self.is_type_ref(rv.base, bare=False):
- return True
- if self.is_none_alias(rv):
- return True
- if allow_none and isinstance(rv, NameExpr) and rv.fullname == "builtins.None":
- return True
- if isinstance(rv, OpExpr) and rv.op == "|":
- if self.is_stub_file:
- return True
- if self.can_be_type_alias(rv.left, allow_none=True) and self.can_be_type_alias(
- rv.right, allow_none=True
- ):
- return True
- return False
- def can_possibly_be_type_form(self, s: AssignmentStmt) -> bool:
- """Like can_be_type_alias(), but simpler and doesn't require fully analyzed rvalue.
- Instead, use lvalues/annotations structure to figure out whether this can potentially be
- a type alias definition, NamedTuple, or TypedDict. Another difference from above function
- is that we are only interested IndexExpr, CallExpr and OpExpr rvalues, since only those
- can be potentially recursive (things like `A = A` are never valid).
- """
- if len(s.lvalues) > 1:
- return False
- if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr):
- ref = s.rvalue.callee.fullname
- return ref in TPDICT_NAMES or ref in TYPED_NAMEDTUPLE_NAMES
- if not isinstance(s.lvalues[0], NameExpr):
- return False
- if s.unanalyzed_type is not None and not self.is_pep_613(s):
- return False
- if not isinstance(s.rvalue, (IndexExpr, OpExpr)):
- return False
- # Something that looks like Foo = Bar[Baz, ...]
- return True
- def is_type_ref(self, rv: Expression, bare: bool = False) -> bool:
- """Does this expression refer to a type?
- This includes:
- * Special forms, like Any or Union
- * Classes (except subscripted enums)
- * Other type aliases
- * PlaceholderNodes with becomes_typeinfo=True (these can be not ready class
- definitions, and not ready aliases).
- If bare is True, this is not a base of an index expression, so some special
- forms are not valid (like a bare Union).
- Note: This method should be only used in context of a type alias definition.
- This method can only return True for RefExprs, to check if C[int] is a valid
- target for type alias call this method on expr.base (i.e. on C in C[int]).
- See also can_be_type_alias().
- """
- if not isinstance(rv, RefExpr):
- return False
- if isinstance(rv.node, TypeVarLikeExpr):
- self.fail(f'Type variable "{rv.fullname}" is invalid as target for type alias', rv)
- return False
- if bare:
- # These three are valid even if bare, for example
- # A = Tuple is just equivalent to A = Tuple[Any, ...].
- valid_refs = {"typing.Any", "typing.Tuple", "typing.Callable"}
- else:
- valid_refs = type_constructors
- if isinstance(rv.node, TypeAlias) or rv.fullname in valid_refs:
- return True
- if isinstance(rv.node, TypeInfo):
- if bare:
- return True
- # Assignment color = Color['RED'] defines a variable, not an alias.
- return not rv.node.is_enum
- if isinstance(rv.node, Var):
- return rv.node.fullname in NEVER_NAMES
- if isinstance(rv, NameExpr):
- n = self.lookup(rv.name, rv)
- if n and isinstance(n.node, PlaceholderNode) and n.node.becomes_typeinfo:
- return True
- elif isinstance(rv, MemberExpr):
- fname = get_member_expr_fullname(rv)
- if fname:
- # The r.h.s. for variable definitions may not be a type reference but just
- # an instance attribute, so suppress the errors.
- n = self.lookup_qualified(fname, rv, suppress_errors=True)
- if n and isinstance(n.node, PlaceholderNode) and n.node.becomes_typeinfo:
- return True
- return False
- def is_none_alias(self, node: Expression) -> bool:
- """Is this a r.h.s. for a None alias?
- We special case the assignments like Void = type(None), to allow using
- Void in type annotations.
- """
- if isinstance(node, CallExpr):
- if (
- isinstance(node.callee, NameExpr)
- and len(node.args) == 1
- and isinstance(node.args[0], NameExpr)
- ):
- call = self.lookup_qualified(node.callee.name, node.callee)
- arg = self.lookup_qualified(node.args[0].name, node.args[0])
- if (
- call is not None
- and call.node
- and call.node.fullname == "builtins.type"
- and arg is not None
- and arg.node
- and arg.node.fullname == "builtins.None"
- ):
- return True
- return False
- def record_special_form_lvalue(self, s: AssignmentStmt) -> None:
- """Record minimal necessary information about l.h.s. of a special form.
- This exists mostly for compatibility with the old semantic analyzer.
- """
- lvalue = s.lvalues[0]
- assert isinstance(lvalue, NameExpr)
- lvalue.is_special_form = True
- if self.current_symbol_kind() == GDEF:
- lvalue.fullname = self.qualified_name(lvalue.name)
- lvalue.kind = self.current_symbol_kind()
- def analyze_enum_assign(self, s: AssignmentStmt) -> bool:
- """Check if s defines an Enum."""
- if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, EnumCallExpr):
- # Already analyzed enum -- nothing to do here.
- return True
- return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope())
- def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
- """Check if s defines a namedtuple."""
- if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, NamedTupleExpr):
- if s.rvalue.analyzed.info.tuple_type and not has_placeholder(
- s.rvalue.analyzed.info.tuple_type
- ):
- return True # This is a valid and analyzed named tuple definition, nothing to do here.
- if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
- return False
- lvalue = s.lvalues[0]
- if isinstance(lvalue, MemberExpr):
- if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.callee, RefExpr):
- fullname = s.rvalue.callee.fullname
- if fullname == "collections.namedtuple" or fullname in TYPED_NAMEDTUPLE_NAMES:
- self.fail("NamedTuple type as an attribute is not supported", lvalue)
- return False
- name = lvalue.name
- namespace = self.qualified_name(name)
- with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
- internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple(
- s.rvalue, name, self.is_func_scope()
- )
- if internal_name is None:
- return False
- if internal_name != name:
- self.fail(
- 'First argument to namedtuple() should be "{}", not "{}"'.format(
- name, internal_name
- ),
- s.rvalue,
- code=codes.NAME_MATCH,
- )
- return True
- # Yes, it's a valid namedtuple, but defer if it is not ready.
- if not info:
- self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
- else:
- self.setup_type_vars(info.defn, tvar_defs)
- self.setup_alias_type_vars(info.defn)
- return True
- def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
- """Check if s defines a typed dict."""
- if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, TypedDictExpr):
- if s.rvalue.analyzed.info.typeddict_type and not has_placeholder(
- s.rvalue.analyzed.info.typeddict_type
- ):
- # This is a valid and analyzed typed dict definition, nothing to do here.
- return True
- if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
- return False
- lvalue = s.lvalues[0]
- name = lvalue.name
- namespace = self.qualified_name(name)
- with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
- is_typed_dict, info, tvar_defs = self.typed_dict_analyzer.check_typeddict(
- s.rvalue, name, self.is_func_scope()
- )
- if not is_typed_dict:
- return False
- if isinstance(lvalue, MemberExpr):
- self.fail("TypedDict type as attribute is not supported", lvalue)
- return False
- # Yes, it's a valid typed dict, but defer if it is not ready.
- if not info:
- self.mark_incomplete(name, lvalue, becomes_typeinfo=True)
- else:
- defn = info.defn
- self.setup_type_vars(defn, tvar_defs)
- self.setup_alias_type_vars(defn)
- return True
- def analyze_lvalues(self, s: AssignmentStmt) -> None:
- # We cannot use s.type, because analyze_simple_literal_type() will set it.
- explicit = s.unanalyzed_type is not None
- if self.is_final_type(s.unanalyzed_type):
- # We need to exclude bare Final.
- assert isinstance(s.unanalyzed_type, UnboundType)
- if not s.unanalyzed_type.args:
- explicit = False
- if s.rvalue:
- if isinstance(s.rvalue, TempNode):
- has_explicit_value = not s.rvalue.no_rhs
- else:
- has_explicit_value = True
- else:
- has_explicit_value = False
- for lval in s.lvalues:
- self.analyze_lvalue(
- lval,
- explicit_type=explicit,
- is_final=s.is_final_def,
- has_explicit_value=has_explicit_value,
- )
- def apply_dynamic_class_hook(self, s: AssignmentStmt) -> None:
- if not isinstance(s.rvalue, CallExpr):
- return
- fname = ""
- call = s.rvalue
- while True:
- if isinstance(call.callee, RefExpr):
- fname = call.callee.fullname
- # check if method call
- if not fname and isinstance(call.callee, MemberExpr):
- callee_expr = call.callee.expr
- if isinstance(callee_expr, RefExpr) and callee_expr.fullname:
- method_name = call.callee.name
- fname = callee_expr.fullname + "." + method_name
- elif isinstance(callee_expr, CallExpr):
- # check if chain call
- call = callee_expr
- continue
- break
- if not fname:
- return
- hook = self.plugin.get_dynamic_class_hook(fname)
- if not hook:
- return
- for lval in s.lvalues:
- if not isinstance(lval, NameExpr):
- continue
- hook(DynamicClassDefContext(call, lval.name, self))
- def unwrap_final(self, s: AssignmentStmt) -> bool:
- """Strip Final[...] if present in an assignment.
- This is done to invoke type inference during type checking phase for this
- assignment. Also, Final[...] doesn't affect type in any way -- it is rather an
- access qualifier for given `Var`.
- Also perform various consistency checks.
- Returns True if Final[...] was present.
- """
- if not s.unanalyzed_type or not self.is_final_type(s.unanalyzed_type):
- return False
- assert isinstance(s.unanalyzed_type, UnboundType)
- if len(s.unanalyzed_type.args) > 1:
- self.fail("Final[...] takes at most one type argument", s.unanalyzed_type)
- invalid_bare_final = False
- if not s.unanalyzed_type.args:
- s.type = None
- if isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs:
- invalid_bare_final = True
- self.fail("Type in Final[...] can only be omitted if there is an initializer", s)
- else:
- s.type = s.unanalyzed_type.args[0]
- if s.type is not None and self.is_classvar(s.type):
- self.fail("Variable should not be annotated with both ClassVar and Final", s)
- return False
- if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], RefExpr):
- self.fail("Invalid final declaration", s)
- return False
- lval = s.lvalues[0]
- assert isinstance(lval, RefExpr)
- # Reset inferred status if it was set due to simple literal rvalue on previous iteration.
- # TODO: this is a best-effort quick fix, we should avoid the need to manually sync this,
- # see https://github.com/python/mypy/issues/6458.
- if lval.is_new_def:
- lval.is_inferred_def = s.type is None
- if self.loop_depth[-1] > 0:
- self.fail("Cannot use Final inside a loop", s)
- if self.type and self.type.is_protocol:
- self.msg.protocol_members_cant_be_final(s)
- if (
- isinstance(s.rvalue, TempNode)
- and s.rvalue.no_rhs
- and not self.is_stub_file
- and not self.is_class_scope()
- ):
- if not invalid_bare_final: # Skip extra error messages.
- self.msg.final_without_value(s)
- return True
- def check_final_implicit_def(self, s: AssignmentStmt) -> None:
- """Do basic checks for final declaration on self in __init__.
- Additional re-definition checks are performed by `analyze_lvalue`.
- """
- if not s.is_final_def:
- return
- lval = s.lvalues[0]
- assert isinstance(lval, RefExpr)
- if isinstance(lval, MemberExpr):
- if not self.is_self_member_ref(lval):
- self.fail("Final can be only applied to a name or an attribute on self", s)
- s.is_final_def = False
- return
- else:
- assert self.function_stack
- if self.function_stack[-1].name != "__init__":
- self.fail("Can only declare a final attribute in class body or __init__", s)
- s.is_final_def = False
- return
- def store_final_status(self, s: AssignmentStmt) -> None:
- """If this is a locally valid final declaration, set the corresponding flag on `Var`."""
- if s.is_final_def:
- if len(s.lvalues) == 1 and isinstance(s.lvalues[0], RefExpr):
- node = s.lvalues[0].node
- if isinstance(node, Var):
- node.is_final = True
- if s.type:
- node.final_value = constant_fold_expr(s.rvalue, self.cur_mod_id)
- if self.is_class_scope() and (
- isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs
- ):
- node.final_unset_in_class = True
- else:
- for lval in self.flatten_lvalues(s.lvalues):
- # Special case: we are working with an `Enum`:
- #
- # class MyEnum(Enum):
- # key = 'some value'
- #
- # Here `key` is implicitly final. In runtime, code like
- #
- # MyEnum.key = 'modified'
- #
- # will fail with `AttributeError: Cannot reassign members.`
- # That's why we need to replicate this.
- if (
- isinstance(lval, NameExpr)
- and isinstance(self.type, TypeInfo)
- and self.type.is_enum
- ):
- cur_node = self.type.names.get(lval.name, None)
- if (
- cur_node
- and isinstance(cur_node.node, Var)
- and not (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)
- ):
- # Double underscored members are writable on an `Enum`.
- # (Except read-only `__members__` but that is handled in type checker)
- cur_node.node.is_final = s.is_final_def = not is_dunder(cur_node.node.name)
- # Special case: deferred initialization of a final attribute in __init__.
- # In this case we just pretend this is a valid final definition to suppress
- # errors about assigning to final attribute.
- if isinstance(lval, MemberExpr) and self.is_self_member_ref(lval):
- assert self.type, "Self member outside a class"
- cur_node = self.type.names.get(lval.name, None)
- if cur_node and isinstance(cur_node.node, Var) and cur_node.node.is_final:
- assert self.function_stack
- top_function = self.function_stack[-1]
- if (
- top_function.name == "__init__"
- and cur_node.node.final_unset_in_class
- and not cur_node.node.final_set_in_init
- and not (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs)
- ):
- cur_node.node.final_set_in_init = True
- s.is_final_def = True
- def flatten_lvalues(self, lvalues: list[Expression]) -> list[Expression]:
- res: list[Expression] = []
- for lv in lvalues:
- if isinstance(lv, (TupleExpr, ListExpr)):
- res.extend(self.flatten_lvalues(lv.items))
- else:
- res.append(lv)
- return res
- def process_type_annotation(self, s: AssignmentStmt) -> None:
- """Analyze type annotation or infer simple literal type."""
- if s.type:
- lvalue = s.lvalues[-1]
- allow_tuple_literal = isinstance(lvalue, TupleExpr)
- analyzed = self.anal_type(s.type, allow_tuple_literal=allow_tuple_literal)
- # Don't store not ready types (including placeholders).
- if analyzed is None or has_placeholder(analyzed):
- self.defer(s)
- return
- s.type = analyzed
- if (
- self.type
- and self.type.is_protocol
- and isinstance(lvalue, NameExpr)
- and isinstance(s.rvalue, TempNode)
- and s.rvalue.no_rhs
- ):
- if isinstance(lvalue.node, Var):
- lvalue.node.is_abstract_var = True
- else:
- if (
- self.type
- and self.type.is_protocol
- and self.is_annotated_protocol_member(s)
- and not self.is_func_scope()
- ):
- self.fail("All protocol members must have explicitly declared types", s)
- # Set the type if the rvalue is a simple literal (even if the above error occurred).
- if len(s.lvalues) == 1 and isinstance(s.lvalues[0], RefExpr):
- ref_expr = s.lvalues[0]
- safe_literal_inference = True
- if self.type and isinstance(ref_expr, NameExpr) and len(self.type.mro) > 1:
- # Check if there is a definition in supertype. If yes, we can't safely
- # decide here what to infer: int or Literal[42].
- safe_literal_inference = self.type.mro[1].get(ref_expr.name) is None
- if safe_literal_inference and ref_expr.is_inferred_def:
- s.type = self.analyze_simple_literal_type(s.rvalue, s.is_final_def)
- if s.type:
- # Store type into nodes.
- for lvalue in s.lvalues:
- self.store_declared_types(lvalue, s.type)
- def is_annotated_protocol_member(self, s: AssignmentStmt) -> bool:
- """Check whether a protocol member is annotated.
- There are some exceptions that can be left unannotated, like ``__slots__``."""
- return any(
- (isinstance(lv, NameExpr) and lv.name != "__slots__" and lv.is_inferred_def)
- for lv in s.lvalues
- )
- def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Type | None:
- """Return builtins.int if rvalue is an int literal, etc.
- If this is a 'Final' context, we return "Literal[...]" instead.
- """
- if self.function_stack:
- # Skip inside a function; this is to avoid confusing
- # the code that handles dead code due to isinstance()
- # inside type variables with value restrictions (like
- # AnyStr).
- return None
- value = constant_fold_expr(rvalue, self.cur_mod_id)
- if value is None or isinstance(value, complex):
- return None
- if isinstance(value, bool):
- type_name = "builtins.bool"
- elif isinstance(value, int):
- type_name = "builtins.int"
- elif isinstance(value, str):
- type_name = "builtins.str"
- elif isinstance(value, float):
- type_name = "builtins.float"
- typ = self.named_type_or_none(type_name)
- if typ and is_final:
- return typ.copy_modified(last_known_value=LiteralType(value=value, fallback=typ))
- return typ
- def analyze_alias(
- self, name: str, rvalue: Expression, allow_placeholder: bool = False
- ) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str]]:
- """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
- If yes, return the corresponding type, a list of
- qualified type variable names for generic aliases, a set of names the alias depends on,
- and a list of type variables if the alias is generic.
- A schematic example for the dependencies:
- A = int
- B = str
- analyze_alias(Dict[A, B])[2] == {'__main__.A', '__main__.B'}
- """
- dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
- global_scope = not self.type and not self.function_stack
- try:
- typ = expr_to_unanalyzed_type(rvalue, self.options, self.is_stub_file)
- except TypeTranslationError:
- self.fail(
- "Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE
- )
- return None, [], set(), []
- found_type_vars = typ.accept(TypeVarLikeQuery(self, self.tvar_scope))
- tvar_defs: list[TypeVarLikeType] = []
- namespace = self.qualified_name(name)
- with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
- for name, tvar_expr in found_type_vars:
- tvar_def = self.tvar_scope.bind_new(name, tvar_expr)
- tvar_defs.append(tvar_def)
- analyzed, depends_on = analyze_type_alias(
- typ,
- self,
- self.tvar_scope,
- self.plugin,
- self.options,
- self.is_typeshed_stub_file,
- allow_placeholder=allow_placeholder,
- in_dynamic_func=dynamic,
- global_scope=global_scope,
- allowed_alias_tvars=tvar_defs,
- )
- # There can be only one variadic variable at most, the error is reported elsewhere.
- new_tvar_defs = []
- variadic = False
- for td in tvar_defs:
- if isinstance(td, TypeVarTupleType):
- if variadic:
- continue
- variadic = True
- new_tvar_defs.append(td)
- qualified_tvars = [node.fullname for _name, node in found_type_vars]
- return analyzed, new_tvar_defs, depends_on, qualified_tvars
- def is_pep_613(self, s: AssignmentStmt) -> bool:
- if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
- lookup = self.lookup_qualified(s.unanalyzed_type.name, s, suppress_errors=True)
- if lookup and lookup.fullname in TYPE_ALIAS_NAMES:
- return True
- return False
- def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
- """Check if assignment creates a type alias and set it up as needed.
- Return True if it is a type alias (even if the target is not ready),
- or False otherwise.
- Note: the resulting types for subscripted (including generic) aliases
- are also stored in rvalue.analyzed.
- """
- if s.invalid_recursive_alias:
- return True
- lvalue = s.lvalues[0]
- if len(s.lvalues) > 1 or not isinstance(lvalue, NameExpr):
- # First rule: Only simple assignments like Alias = ... create aliases.
- return False
- pep_613 = self.is_pep_613(s)
- if not pep_613 and s.unanalyzed_type is not None:
- # Second rule: Explicit type (cls: Type[A] = A) always creates variable, not alias.
- # unless using PEP 613 `cls: TypeAlias = A`
- return False
- if isinstance(s.rvalue, CallExpr) and s.rvalue.analyzed:
- return False
- existing = self.current_symbol_table().get(lvalue.name)
- # Third rule: type aliases can't be re-defined. For example:
- # A: Type[float] = int
- # A = float # OK, but this doesn't define an alias
- # B = int
- # B = float # Error!
- # Don't create an alias in these cases:
- if existing and (
- isinstance(existing.node, Var) # existing variable
- or (isinstance(existing.node, TypeAlias) and not s.is_alias_def) # existing alias
- or (isinstance(existing.node, PlaceholderNode) and existing.node.node.line < s.line)
- ): # previous incomplete definition
- # TODO: find a more robust way to track the order of definitions.
- # Note: if is_alias_def=True, this is just a node from previous iteration.
- if isinstance(existing.node, TypeAlias) and not s.is_alias_def:
- self.fail(
- 'Cannot assign multiple types to name "{}"'
- ' without an explicit "Type[...]" annotation'.format(lvalue.name),
- lvalue,
- )
- return False
- non_global_scope = self.type or self.is_func_scope()
- if not pep_613 and isinstance(s.rvalue, RefExpr) and non_global_scope:
- # Fourth rule (special case): Non-subscripted right hand side creates a variable
- # at class and function scopes. For example:
- #
- # class Model:
- # ...
- # class C:
- # model = Model # this is automatically a variable with type 'Type[Model]'
- #
- # without this rule, this typical use case will require a lot of explicit
- # annotations (see the second rule).
- return False
- rvalue = s.rvalue
- if not pep_613 and not self.can_be_type_alias(rvalue):
- return False
- if existing and not isinstance(existing.node, (PlaceholderNode, TypeAlias)):
- # Cannot redefine existing node as type alias.
- return False
- res: Type | None = None
- if self.is_none_alias(rvalue):
- res = NoneType()
- alias_tvars: list[TypeVarLikeType] = []
- depends_on: set[str] = set()
- qualified_tvars: list[str] = []
- else:
- tag = self.track_incomplete_refs()
- res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias(
- lvalue.name, rvalue, allow_placeholder=True
- )
- if not res:
- return False
- if not self.options.disable_recursive_aliases and not self.is_func_scope():
- # Only marking incomplete for top-level placeholders makes recursive aliases like
- # `A = Sequence[str | A]` valid here, similar to how we treat base classes in class
- # definitions, allowing `class str(Sequence[str]): ...`
- incomplete_target = isinstance(res, ProperType) and isinstance(
- res, PlaceholderType
- )
- else:
- incomplete_target = has_placeholder(res)
- if self.found_incomplete_ref(tag) or incomplete_target:
- # Since we have got here, we know this must be a type alias (incomplete refs
- # may appear in nested positions), therefore use becomes_typeinfo=True.
- self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True)
- return True
- self.add_type_alias_deps(depends_on)
- # In addition to the aliases used, we add deps on unbound
- # type variables, since they are erased from target type.
- self.add_type_alias_deps(qualified_tvars)
- # The above are only direct deps on other aliases.
- # For subscripted aliases, type deps from expansion are added in deps.py
- # (because the type is stored).
- check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s)
- # When this type alias gets "inlined", the Any is not explicit anymore,
- # so we need to replace it with non-explicit Anys.
- res = make_any_non_explicit(res)
- # Note: with the new (lazy) type alias representation we only need to set no_args to True
- # if the expected number of arguments is non-zero, so that aliases like A = List work.
- # However, eagerly expanding aliases like Text = str is a nice performance optimization.
- no_args = isinstance(res, Instance) and not res.args # type: ignore[misc]
- fix_instance_types(res, self.fail, self.note, self.options)
- # Aliases defined within functions can't be accessed outside
- # the function, since the symbol table will no longer
- # exist. Work around by expanding them eagerly when used.
- eager = self.is_func_scope()
- alias_node = TypeAlias(
- res,
- self.qualified_name(lvalue.name),
- s.line,
- s.column,
- alias_tvars=alias_tvars,
- no_args=no_args,
- eager=eager,
- )
- if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)) and (
- not isinstance(rvalue, OpExpr)
- or (self.options.python_version >= (3, 10) or self.is_stub_file)
- ):
- # Note: CallExpr is for "void = type(None)" and OpExpr is for "X | Y" union syntax.
- s.rvalue.analyzed = TypeAliasExpr(alias_node)
- s.rvalue.analyzed.line = s.line
- # we use the column from resulting target, to get better location for errors
- s.rvalue.analyzed.column = res.column
- elif isinstance(s.rvalue, RefExpr):
- s.rvalue.is_alias_rvalue = True
- if existing:
- # An alias gets updated.
- updated = False
- if isinstance(existing.node, TypeAlias):
- if existing.node.target != res:
- # Copy expansion to the existing alias, this matches how we update base classes
- # for a TypeInfo _in place_ if there are nested placeholders.
- existing.node.target = res
- existing.node.alias_tvars = alias_tvars
- existing.node.no_args = no_args
- updated = True
- else:
- # Otherwise just replace existing placeholder with type alias.
- existing.node = alias_node
- updated = True
- if updated:
- if self.final_iteration:
- self.cannot_resolve_name(lvalue.name, "name", s)
- return True
- else:
- # We need to defer so that this change can get propagated to base classes.
- self.defer(s, force_progress=True)
- else:
- self.add_symbol(lvalue.name, alias_node, s)
- if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias):
- alias_node.normalized = rvalue.node.normalized
- current_node = existing.node if existing else alias_node
- assert isinstance(current_node, TypeAlias)
- self.disable_invalid_recursive_aliases(s, current_node)
- if self.is_class_scope():
- assert self.type is not None
- if self.type.is_protocol:
- self.fail("Type aliases are prohibited in protocol bodies", s)
- if not lvalue.name[0].isupper():
- self.note("Use variable annotation syntax to define protocol members", s)
- return True
- def disable_invalid_recursive_aliases(
- self, s: AssignmentStmt, current_node: TypeAlias
- ) -> None:
- """Prohibit and fix recursive type aliases that are invalid/unsupported."""
- messages = []
- if is_invalid_recursive_alias({current_node}, current_node.target):
- messages.append("Invalid recursive alias: a union item of itself")
- if detect_diverging_alias(
- current_node, current_node.target, self.lookup_qualified, self.tvar_scope
- ):
- messages.append("Invalid recursive alias: type variable nesting on right hand side")
- if messages:
- current_node.target = AnyType(TypeOfAny.from_error)
- s.invalid_recursive_alias = True
- for msg in messages:
- self.fail(msg, s.rvalue)
- def analyze_lvalue(
- self,
- lval: Lvalue,
- nested: bool = False,
- explicit_type: bool = False,
- is_final: bool = False,
- escape_comprehensions: bool = False,
- has_explicit_value: bool = False,
- ) -> None:
- """Analyze an lvalue or assignment target.
- Args:
- lval: The target lvalue
- nested: If true, the lvalue is within a tuple or list lvalue expression
- explicit_type: Assignment has type annotation
- escape_comprehensions: If we are inside a comprehension, set the variable
- in the enclosing scope instead. This implements
- https://www.python.org/dev/peps/pep-0572/#scope-of-the-target
- """
- if escape_comprehensions:
- assert isinstance(lval, NameExpr), "assignment expression target must be NameExpr"
- if isinstance(lval, NameExpr):
- self.analyze_name_lvalue(
- lval,
- explicit_type,
- is_final,
- escape_comprehensions,
- has_explicit_value=has_explicit_value,
- )
- elif isinstance(lval, MemberExpr):
- self.analyze_member_lvalue(lval, explicit_type, is_final, has_explicit_value)
- if explicit_type and not self.is_self_member_ref(lval):
- self.fail("Type cannot be declared in assignment to non-self attribute", lval)
- elif isinstance(lval, IndexExpr):
- if explicit_type:
- self.fail("Unexpected type declaration", lval)
- lval.accept(self)
- elif isinstance(lval, TupleExpr):
- self.analyze_tuple_or_list_lvalue(lval, explicit_type)
- elif isinstance(lval, StarExpr):
- if nested:
- self.analyze_lvalue(lval.expr, nested, explicit_type)
- else:
- self.fail("Starred assignment target must be in a list or tuple", lval)
- else:
- self.fail("Invalid assignment target", lval)
- def analyze_name_lvalue(
- self,
- lvalue: NameExpr,
- explicit_type: bool,
- is_final: bool,
- escape_comprehensions: bool,
- has_explicit_value: bool,
- ) -> None:
- """Analyze an lvalue that targets a name expression.
- Arguments are similar to "analyze_lvalue".
- """
- if lvalue.node:
- # This has been bound already in a previous iteration.
- return
- name = lvalue.name
- if self.is_alias_for_final_name(name):
- if is_final:
- self.fail("Cannot redefine an existing name as final", lvalue)
- else:
- self.msg.cant_assign_to_final(name, self.type is not None, lvalue)
- kind = self.current_symbol_kind()
- names = self.current_symbol_table(escape_comprehensions=escape_comprehensions)
- existing = names.get(name)
- outer = self.is_global_or_nonlocal(name)
- if kind == MDEF and isinstance(self.type, TypeInfo) and self.type.is_enum:
- # Special case: we need to be sure that `Enum` keys are unique.
- if existing is not None and not isinstance(existing.node, PlaceholderNode):
- self.fail(
- 'Attempted to reuse member name "{}" in Enum definition "{}"'.format(
- name, self.type.name
- ),
- lvalue,
- )
- if (not existing or isinstance(existing.node, PlaceholderNode)) and not outer:
- # Define new variable.
- var = self.make_name_lvalue_var(lvalue, kind, not explicit_type, has_explicit_value)
- added = self.add_symbol(name, var, lvalue, escape_comprehensions=escape_comprehensions)
- # Only bind expression if we successfully added name to symbol table.
- if added:
- lvalue.is_new_def = True
- lvalue.is_inferred_def = True
- lvalue.kind = kind
- lvalue.node = var
- if kind == GDEF:
- lvalue.fullname = var._fullname
- else:
- lvalue.fullname = lvalue.name
- if self.is_func_scope():
- if unmangle(name) == "_":
- # Special case for assignment to local named '_': always infer 'Any'.
- typ = AnyType(TypeOfAny.special_form)
- self.store_declared_types(lvalue, typ)
- if is_final and self.is_final_redefinition(kind, name):
- self.fail("Cannot redefine an existing name as final", lvalue)
- else:
- self.make_name_lvalue_point_to_existing_def(lvalue, explicit_type, is_final)
- def is_final_redefinition(self, kind: int, name: str) -> bool:
- if kind == GDEF:
- return self.is_mangled_global(name) and not self.is_initial_mangled_global(name)
- elif kind == MDEF and self.type:
- return unmangle(name) + "'" in self.type.names
- return False
- def is_alias_for_final_name(self, name: str) -> bool:
- if self.is_func_scope():
- if not name.endswith("'"):
- # Not a mangled name -- can't be an alias
- return False
- name = unmangle(name)
- assert self.locals[-1] is not None, "No locals at function scope"
- existing = self.locals[-1].get(name)
- return existing is not None and is_final_node(existing.node)
- elif self.type is not None:
- orig_name = unmangle(name) + "'"
- if name == orig_name:
- return False
- existing = self.type.names.get(orig_name)
- return existing is not None and is_final_node(existing.node)
- else:
- orig_name = unmangle(name) + "'"
- if name == orig_name:
- return False
- existing = self.globals.get(orig_name)
- return existing is not None and is_final_node(existing.node)
- def make_name_lvalue_var(
- self, lvalue: NameExpr, kind: int, inferred: bool, has_explicit_value: bool
- ) -> Var:
- """Return a Var node for an lvalue that is a name expression."""
- name = lvalue.name
- v = Var(name)
- v.set_line(lvalue)
- v.is_inferred = inferred
- if kind == MDEF:
- assert self.type is not None
- v.info = self.type
- v.is_initialized_in_class = True
- v.allow_incompatible_override = name in ALLOW_INCOMPATIBLE_OVERRIDE
- if kind != LDEF:
- v._fullname = self.qualified_name(name)
- else:
- # fullanme should never stay None
- v._fullname = name
- v.is_ready = False # Type not inferred yet
- v.has_explicit_value = has_explicit_value
- return v
- def make_name_lvalue_point_to_existing_def(
- self, lval: NameExpr, explicit_type: bool, is_final: bool
- ) -> None:
- """Update an lvalue to point to existing definition in the same scope.
- Arguments are similar to "analyze_lvalue".
- Assume that an existing name exists.
- """
- if is_final:
- # Redefining an existing name with final is always an error.
- self.fail("Cannot redefine an existing name as final", lval)
- original_def = self.lookup(lval.name, lval, suppress_errors=True)
- if original_def is None and self.type and not self.is_func_scope():
- # Workaround to allow "x, x = ..." in class body.
- original_def = self.type.get(lval.name)
- if explicit_type:
- # Don't re-bind if there is a type annotation.
- self.name_already_defined(lval.name, lval, original_def)
- else:
- # Bind to an existing name.
- if original_def:
- self.bind_name_expr(lval, original_def)
- else:
- self.name_not_defined(lval.name, lval)
- self.check_lvalue_validity(lval.node, lval)
- def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, explicit_type: bool = False) -> None:
- """Analyze an lvalue or assignment target that is a list or tuple."""
- items = lval.items
- star_exprs = [item for item in items if isinstance(item, StarExpr)]
- if len(star_exprs) > 1:
- self.fail("Two starred expressions in assignment", lval)
- else:
- if len(star_exprs) == 1:
- star_exprs[0].valid = True
- for i in items:
- self.analyze_lvalue(
- lval=i,
- nested=True,
- explicit_type=explicit_type,
- # Lists and tuples always have explicit values defined:
- # `a, b, c = value`
- has_explicit_value=True,
- )
- def analyze_member_lvalue(
- self, lval: MemberExpr, explicit_type: bool, is_final: bool, has_explicit_value: bool
- ) -> None:
- """Analyze lvalue that is a member expression.
- Arguments:
- lval: The target lvalue
- explicit_type: Assignment has type annotation
- is_final: Is the target final
- """
- if lval.node:
- # This has been bound already in a previous iteration.
- return
- lval.accept(self)
- if self.is_self_member_ref(lval):
- assert self.type, "Self member outside a class"
- cur_node = self.type.names.get(lval.name)
- node = self.type.get(lval.name)
- if cur_node and is_final:
- # Overrides will be checked in type checker.
- self.fail("Cannot redefine an existing name as final", lval)
- # On first encounter with this definition, if this attribute was defined before
- # with an inferred type and it's marked with an explicit type now, give an error.
- if (
- not lval.node
- and cur_node
- and isinstance(cur_node.node, Var)
- and cur_node.node.is_inferred
- and explicit_type
- ):
- self.attribute_already_defined(lval.name, lval, cur_node)
- if self.type.is_protocol and has_explicit_value and cur_node is not None:
- # Make this variable non-abstract, it would be safer to do this only if we
- # are inside __init__, but we do this always to preserve historical behaviour.
- if isinstance(cur_node.node, Var):
- cur_node.node.is_abstract_var = False
- if (
- # If the attribute of self is not defined, create a new Var, ...
- node is None
- # ... or if it is defined as abstract in a *superclass*.
- or (cur_node is None and isinstance(node.node, Var) and node.node.is_abstract_var)
- # ... also an explicit declaration on self also creates a new Var.
- # Note that `explicit_type` might have been erased for bare `Final`,
- # so we also check if `is_final` is passed.
- or (cur_node is None and (explicit_type or is_final))
- ):
- if self.type.is_protocol and node is None:
- self.fail("Protocol members cannot be defined via assignment to self", lval)
- else:
- # Implicit attribute definition in __init__.
- lval.is_new_def = True
- lval.is_inferred_def = True
- v = Var(lval.name)
- v.set_line(lval)
- v._fullname = self.qualified_name(lval.name)
- v.info = self.type
- v.is_ready = False
- v.explicit_self_type = explicit_type or is_final
- lval.def_var = v
- lval.node = v
- # TODO: should we also set lval.kind = MDEF?
- self.type.names[lval.name] = SymbolTableNode(MDEF, v, implicit=True)
- self.check_lvalue_validity(lval.node, lval)
- def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
- """Does memberexpr to refer to an attribute of self?"""
- if not isinstance(memberexpr.expr, NameExpr):
- return False
- node = memberexpr.expr.node
- return isinstance(node, Var) and node.is_self
- def check_lvalue_validity(self, node: Expression | SymbolNode | None, ctx: Context) -> None:
- if isinstance(node, TypeVarExpr):
- self.fail("Invalid assignment target", ctx)
- elif isinstance(node, TypeInfo):
- self.fail(message_registry.CANNOT_ASSIGN_TO_TYPE, ctx)
- def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None:
- if isinstance(lvalue, RefExpr):
- lvalue.is_inferred_def = False
- if isinstance(lvalue.node, Var):
- var = lvalue.node
- var.type = typ
- var.is_ready = True
- typ = get_proper_type(typ)
- if (
- var.is_final
- and isinstance(typ, Instance)
- and typ.last_known_value
- and (not self.type or not self.type.is_enum)
- ):
- var.final_value = typ.last_known_value.value
- # If node is not a variable, we'll catch it elsewhere.
- elif isinstance(lvalue, TupleExpr):
- typ = get_proper_type(typ)
- if isinstance(typ, TupleType):
- if len(lvalue.items) != len(typ.items):
- self.fail("Incompatible number of tuple items", lvalue)
- return
- for item, itemtype in zip(lvalue.items, typ.items):
- self.store_declared_types(item, itemtype)
- else:
- self.fail("Tuple type expected for multiple variables", lvalue)
- elif isinstance(lvalue, StarExpr):
- # Historical behavior for the old parser
- self.store_declared_types(lvalue.expr, typ)
- else:
- # This has been flagged elsewhere as an error, so just ignore here.
- pass
- def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
- """Check if s declares a TypeVar; it yes, store it in symbol table.
- Return True if this looks like a type variable declaration (but maybe
- with errors), otherwise return False.
- """
- call = self.get_typevarlike_declaration(s, ("typing.TypeVar", "typing_extensions.TypeVar"))
- if not call:
- return False
- name = self.extract_typevarlike_name(s, call)
- if name is None:
- return False
- # Constraining types
- n_values = call.arg_kinds[1:].count(ARG_POS)
- values = self.analyze_value_types(call.args[1 : 1 + n_values])
- res = self.process_typevar_parameters(
- call.args[1 + n_values :],
- call.arg_names[1 + n_values :],
- call.arg_kinds[1 + n_values :],
- n_values,
- s,
- )
- if res is None:
- return False
- variance, upper_bound, default = res
- existing = self.current_symbol_table().get(name)
- if existing and not (
- isinstance(existing.node, PlaceholderNode)
- or
- # Also give error for another type variable with the same name.
- (isinstance(existing.node, TypeVarExpr) and existing.node is call.analyzed)
- ):
- self.fail(f'Cannot redefine "{name}" as a type variable', s)
- return False
- if self.options.disallow_any_unimported:
- for idx, constraint in enumerate(values, start=1):
- if has_any_from_unimported_type(constraint):
- prefix = f"Constraint {idx}"
- self.msg.unimported_type_becomes_any(prefix, constraint, s)
- if has_any_from_unimported_type(upper_bound):
- prefix = "Upper bound of type variable"
- self.msg.unimported_type_becomes_any(prefix, upper_bound, s)
- for t in values + [upper_bound, default]:
- check_for_explicit_any(
- t, self.options, self.is_typeshed_stub_file, self.msg, context=s
- )
- # mypyc suppresses making copies of a function to check each
- # possible type, so set the upper bound to Any to prevent that
- # from causing errors.
- if values and self.options.mypyc:
- upper_bound = AnyType(TypeOfAny.implementation_artifact)
- # Yes, it's a valid type variable definition! Add it to the symbol table.
- if not call.analyzed:
- type_var = TypeVarExpr(
- name, self.qualified_name(name), values, upper_bound, default, variance
- )
- type_var.line = call.line
- call.analyzed = type_var
- updated = True
- else:
- assert isinstance(call.analyzed, TypeVarExpr)
- updated = (
- values != call.analyzed.values
- or upper_bound != call.analyzed.upper_bound
- or default != call.analyzed.default
- )
- call.analyzed.upper_bound = upper_bound
- call.analyzed.values = values
- call.analyzed.default = default
- if any(has_placeholder(v) for v in values):
- self.process_placeholder(None, "TypeVar values", s, force_progress=updated)
- elif has_placeholder(upper_bound):
- self.process_placeholder(None, "TypeVar upper bound", s, force_progress=updated)
- elif has_placeholder(default):
- self.process_placeholder(None, "TypeVar default", s, force_progress=updated)
- self.add_symbol(name, call.analyzed, s)
- return True
- def check_typevarlike_name(self, call: CallExpr, name: str, context: Context) -> bool:
- """Checks that the name of a TypeVar or ParamSpec matches its variable."""
- name = unmangle(name)
- assert isinstance(call.callee, RefExpr)
- typevarlike_type = (
- call.callee.name if isinstance(call.callee, NameExpr) else call.callee.fullname
- )
- if len(call.args) < 1:
- self.fail(f"Too few arguments for {typevarlike_type}()", context)
- return False
- if not isinstance(call.args[0], StrExpr) or not call.arg_kinds[0] == ARG_POS:
- self.fail(f"{typevarlike_type}() expects a string literal as first argument", context)
- return False
- elif call.args[0].value != name:
- msg = 'String argument 1 "{}" to {}(...) does not match variable name "{}"'
- self.fail(msg.format(call.args[0].value, typevarlike_type, name), context)
- return False
- return True
- def get_typevarlike_declaration(
- self, s: AssignmentStmt, typevarlike_types: tuple[str, ...]
- ) -> CallExpr | None:
- """Returns the call expression if `s` is a declaration of `typevarlike_type`
- (TypeVar or ParamSpec), or None otherwise.
- """
- if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
- return None
- if not isinstance(s.rvalue, CallExpr):
- return None
- call = s.rvalue
- callee = call.callee
- if not isinstance(callee, RefExpr):
- return None
- if callee.fullname not in typevarlike_types:
- return None
- return call
- def process_typevar_parameters(
- self,
- args: list[Expression],
- names: list[str | None],
- kinds: list[ArgKind],
- num_values: int,
- context: Context,
- ) -> tuple[int, Type, Type] | None:
- has_values = num_values > 0
- covariant = False
- contravariant = False
- upper_bound: Type = self.object_type()
- default: Type = AnyType(TypeOfAny.from_omitted_generics)
- for param_value, param_name, param_kind in zip(args, names, kinds):
- if not param_kind.is_named():
- self.fail(message_registry.TYPEVAR_UNEXPECTED_ARGUMENT, context)
- return None
- if param_name == "covariant":
- if isinstance(param_value, NameExpr) and param_value.name in ("True", "False"):
- covariant = param_value.name == "True"
- else:
- self.fail(message_registry.TYPEVAR_VARIANCE_DEF.format("covariant"), context)
- return None
- elif param_name == "contravariant":
- if isinstance(param_value, NameExpr) and param_value.name in ("True", "False"):
- contravariant = param_value.name == "True"
- else:
- self.fail(
- message_registry.TYPEVAR_VARIANCE_DEF.format("contravariant"), context
- )
- return None
- elif param_name == "bound":
- if has_values:
- self.fail("TypeVar cannot have both values and an upper bound", context)
- return None
- tv_arg = self.get_typevarlike_argument("TypeVar", param_name, param_value, context)
- if tv_arg is None:
- return None
- upper_bound = tv_arg
- elif param_name == "default":
- tv_arg = self.get_typevarlike_argument(
- "TypeVar", param_name, param_value, context, allow_unbound_tvars=True
- )
- default = tv_arg or AnyType(TypeOfAny.from_error)
- elif param_name == "values":
- # Probably using obsolete syntax with values=(...). Explain the current syntax.
- self.fail('TypeVar "values" argument not supported', context)
- self.fail(
- "Use TypeVar('T', t, ...) instead of TypeVar('T', values=(t, ...))", context
- )
- return None
- else:
- self.fail(
- f'{message_registry.TYPEVAR_UNEXPECTED_ARGUMENT}: "{param_name}"', context
- )
- return None
- if covariant and contravariant:
- self.fail("TypeVar cannot be both covariant and contravariant", context)
- return None
- elif num_values == 1:
- self.fail("TypeVar cannot have only a single constraint", context)
- return None
- elif covariant:
- variance = COVARIANT
- elif contravariant:
- variance = CONTRAVARIANT
- else:
- variance = INVARIANT
- return variance, upper_bound, default
- def get_typevarlike_argument(
- self,
- typevarlike_name: str,
- param_name: str,
- param_value: Expression,
- context: Context,
- *,
- allow_unbound_tvars: bool = False,
- allow_param_spec_literals: bool = False,
- report_invalid_typevar_arg: bool = True,
- ) -> ProperType | None:
- try:
- # We want to use our custom error message below, so we suppress
- # the default error message for invalid types here.
- analyzed = self.expr_to_analyzed_type(
- param_value,
- allow_placeholder=True,
- report_invalid_types=False,
- allow_unbound_tvars=allow_unbound_tvars,
- allow_param_spec_literals=allow_param_spec_literals,
- )
- if analyzed is None:
- # Type variables are special: we need to place them in the symbol table
- # soon, even if upper bound is not ready yet. Otherwise avoiding
- # a "deadlock" in this common pattern would be tricky:
- # T = TypeVar('T', bound=Custom[Any])
- # class Custom(Generic[T]):
- # ...
- analyzed = PlaceholderType(None, [], context.line)
- typ = get_proper_type(analyzed)
- if report_invalid_typevar_arg and isinstance(typ, AnyType) and typ.is_from_error:
- self.fail(
- message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
- param_value,
- )
- # Note: we do not return 'None' here -- we want to continue
- # using the AnyType.
- return typ
- except TypeTranslationError:
- if report_invalid_typevar_arg:
- self.fail(
- message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
- param_value,
- )
- return None
- def extract_typevarlike_name(self, s: AssignmentStmt, call: CallExpr) -> str | None:
- if not call:
- return None
- lvalue = s.lvalues[0]
- assert isinstance(lvalue, NameExpr)
- if s.type:
- self.fail("Cannot declare the type of a TypeVar or similar construct", s)
- return None
- if not self.check_typevarlike_name(call, lvalue.name, s):
- return None
- return lvalue.name
- def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
- """Checks if s declares a ParamSpec; if yes, store it in symbol table.
- Return True if this looks like a ParamSpec (maybe with errors), otherwise return False.
- In the future, ParamSpec may accept bounds and variance arguments, in which
- case more aggressive sharing of code with process_typevar_declaration should be pursued.
- """
- call = self.get_typevarlike_declaration(
- s, ("typing_extensions.ParamSpec", "typing.ParamSpec")
- )
- if not call:
- return False
- name = self.extract_typevarlike_name(s, call)
- if name is None:
- return False
- n_values = call.arg_kinds[1:].count(ARG_POS)
- if n_values != 0:
- self.fail('Too many positional arguments for "ParamSpec"', s)
- default: Type = AnyType(TypeOfAny.from_omitted_generics)
- for param_value, param_name in zip(
- call.args[1 + n_values :], call.arg_names[1 + n_values :]
- ):
- if param_name == "default":
- tv_arg = self.get_typevarlike_argument(
- "ParamSpec",
- param_name,
- param_value,
- s,
- allow_unbound_tvars=True,
- allow_param_spec_literals=True,
- report_invalid_typevar_arg=False,
- )
- default = tv_arg or AnyType(TypeOfAny.from_error)
- if isinstance(tv_arg, Parameters):
- for i, arg_type in enumerate(tv_arg.arg_types):
- typ = get_proper_type(arg_type)
- if isinstance(typ, AnyType) and typ.is_from_error:
- self.fail(
- f"Argument {i} of ParamSpec default must be a type", param_value
- )
- elif (
- isinstance(default, AnyType)
- and default.is_from_error
- or not isinstance(default, (AnyType, UnboundType))
- ):
- self.fail(
- "The default argument to ParamSpec must be a list expression, ellipsis, or a ParamSpec",
- param_value,
- )
- default = AnyType(TypeOfAny.from_error)
- else:
- # ParamSpec is different from a regular TypeVar:
- # arguments are not semantically valid. But, allowed in runtime.
- # So, we need to warn users about possible invalid usage.
- self.fail(
- "The variance and bound arguments to ParamSpec do not have defined semantics yet",
- s,
- )
- # PEP 612 reserves the right to define bound, covariant and contravariant arguments to
- # ParamSpec in a later PEP. If and when that happens, we should do something
- # on the lines of process_typevar_parameters
- if not call.analyzed:
- paramspec_var = ParamSpecExpr(
- name, self.qualified_name(name), self.object_type(), default, INVARIANT
- )
- paramspec_var.line = call.line
- call.analyzed = paramspec_var
- updated = True
- else:
- assert isinstance(call.analyzed, ParamSpecExpr)
- updated = default != call.analyzed.default
- call.analyzed.default = default
- if has_placeholder(default):
- self.process_placeholder(None, "ParamSpec default", s, force_progress=updated)
- self.add_symbol(name, call.analyzed, s)
- return True
- def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool:
- """Checks if s declares a TypeVarTuple; if yes, store it in symbol table.
- Return True if this looks like a TypeVarTuple (maybe with errors), otherwise return False.
- """
- call = self.get_typevarlike_declaration(
- s, ("typing_extensions.TypeVarTuple", "typing.TypeVarTuple")
- )
- if not call:
- return False
- n_values = call.arg_kinds[1:].count(ARG_POS)
- if n_values != 0:
- self.fail('Too many positional arguments for "TypeVarTuple"', s)
- default: Type = AnyType(TypeOfAny.from_omitted_generics)
- for param_value, param_name in zip(
- call.args[1 + n_values :], call.arg_names[1 + n_values :]
- ):
- if param_name == "default":
- tv_arg = self.get_typevarlike_argument(
- "TypeVarTuple",
- param_name,
- param_value,
- s,
- allow_unbound_tvars=True,
- report_invalid_typevar_arg=False,
- )
- default = tv_arg or AnyType(TypeOfAny.from_error)
- if not isinstance(default, UnpackType):
- self.fail(
- "The default argument to TypeVarTuple must be an Unpacked tuple",
- param_value,
- )
- default = AnyType(TypeOfAny.from_error)
- else:
- self.fail(f'Unexpected keyword argument "{param_name}" for "TypeVarTuple"', s)
- if not self.incomplete_feature_enabled(TYPE_VAR_TUPLE, s):
- return False
- name = self.extract_typevarlike_name(s, call)
- if name is None:
- return False
- # PEP 646 does not specify the behavior of variance, constraints, or bounds.
- if not call.analyzed:
- tuple_fallback = self.named_type("builtins.tuple", [self.object_type()])
- typevartuple_var = TypeVarTupleExpr(
- name,
- self.qualified_name(name),
- self.object_type(),
- tuple_fallback,
- default,
- INVARIANT,
- )
- typevartuple_var.line = call.line
- call.analyzed = typevartuple_var
- updated = True
- else:
- assert isinstance(call.analyzed, TypeVarTupleExpr)
- updated = default != call.analyzed.default
- call.analyzed.default = default
- if has_placeholder(default):
- self.process_placeholder(None, "TypeVarTuple default", s, force_progress=updated)
- self.add_symbol(name, call.analyzed, s)
- return True
- def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
- if self.is_func_scope() and not self.type and "@" not in name:
- name += "@" + str(line)
- class_def = ClassDef(name, Block([]))
- if self.is_func_scope() and not self.type:
- # Full names of generated classes should always be prefixed with the module names
- # even if they are nested in a function, since these classes will be (de-)serialized.
- # (Note that the caller should append @line to the name to avoid collisions.)
- # TODO: clean this up, see #6422.
- class_def.fullname = self.cur_mod_id + "." + self.qualified_name(name)
- else:
- class_def.fullname = self.qualified_name(name)
- info = TypeInfo(SymbolTable(), class_def, self.cur_mod_id)
- class_def.info = info
- mro = basetype_or_fallback.type.mro
- if not mro:
- # Probably an error, we should not crash so generate something meaningful.
- mro = [basetype_or_fallback.type, self.object_type().type]
- info.mro = [info] + mro
- info.bases = [basetype_or_fallback]
- return info
- def analyze_value_types(self, items: list[Expression]) -> list[Type]:
- """Analyze types from values expressions in type variable definition."""
- result: list[Type] = []
- for node in items:
- try:
- analyzed = self.anal_type(
- self.expr_to_unanalyzed_type(node), allow_placeholder=True
- )
- if analyzed is None:
- # Type variables are special: we need to place them in the symbol table
- # soon, even if some value is not ready yet, see process_typevar_parameters()
- # for an example.
- analyzed = PlaceholderType(None, [], node.line)
- result.append(analyzed)
- except TypeTranslationError:
- self.fail("Type expected", node)
- result.append(AnyType(TypeOfAny.from_error))
- return result
- def check_classvar(self, s: AssignmentStmt) -> None:
- """Check if assignment defines a class variable."""
- lvalue = s.lvalues[0]
- if len(s.lvalues) != 1 or not isinstance(lvalue, RefExpr):
- return
- if not s.type or not self.is_classvar(s.type):
- return
- if self.is_class_scope() and isinstance(lvalue, NameExpr):
- node = lvalue.node
- if isinstance(node, Var):
- node.is_classvar = True
- analyzed = self.anal_type(s.type)
- assert self.type is not None
- if analyzed is not None and set(get_type_vars(analyzed)) & set(
- self.type.defn.type_vars
- ):
- # This means that we have a type var defined inside of a ClassVar.
- # This is not allowed by PEP526.
- # See https://github.com/python/mypy/issues/11538
- self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s)
- if (
- analyzed is not None
- and self.type.self_type in get_type_vars(analyzed)
- and self.type.defn.type_vars
- ):
- self.fail(message_registry.CLASS_VAR_WITH_GENERIC_SELF, s)
- elif not isinstance(lvalue, MemberExpr) or self.is_self_member_ref(lvalue):
- # In case of member access, report error only when assigning to self
- # Other kinds of member assignments should be already reported
- self.fail_invalid_classvar(lvalue)
- def is_classvar(self, typ: Type) -> bool:
- if not isinstance(typ, UnboundType):
- return False
- sym = self.lookup_qualified(typ.name, typ)
- if not sym or not sym.node:
- return False
- return sym.node.fullname == "typing.ClassVar"
- def is_final_type(self, typ: Type | None) -> bool:
- if not isinstance(typ, UnboundType):
- return False
- sym = self.lookup_qualified(typ.name, typ)
- if not sym or not sym.node:
- return False
- return sym.node.fullname in FINAL_TYPE_NAMES
- def fail_invalid_classvar(self, context: Context) -> None:
- self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context)
- def process_module_assignment(
- self, lvals: list[Lvalue], rval: Expression, ctx: AssignmentStmt
- ) -> None:
- """Propagate module references across assignments.
- Recursively handles the simple form of iterable unpacking; doesn't
- handle advanced unpacking with *rest, dictionary unpacking, etc.
- In an expression like x = y = z, z is the rval and lvals will be [x,
- y].
- """
- if isinstance(rval, (TupleExpr, ListExpr)) and all(
- isinstance(v, TupleExpr) for v in lvals
- ):
- # rval and all lvals are either list or tuple, so we are dealing
- # with unpacking assignment like `x, y = a, b`. Mypy didn't
- # understand our all(isinstance(...)), so cast them as TupleExpr
- # so mypy knows it is safe to access their .items attribute.
- seq_lvals = cast(List[TupleExpr], lvals)
- # given an assignment like:
- # (x, y) = (m, n) = (a, b)
- # we now have:
- # seq_lvals = [(x, y), (m, n)]
- # seq_rval = (a, b)
- # We now zip this into:
- # elementwise_assignments = [(a, x, m), (b, y, n)]
- # where each elementwise assignment includes one element of rval and the
- # corresponding element of each lval. Basically we unpack
- # (x, y) = (m, n) = (a, b)
- # into elementwise assignments
- # x = m = a
- # y = n = b
- # and then we recursively call this method for each of those assignments.
- # If the rval and all lvals are not all of the same length, zip will just ignore
- # extra elements, so no error will be raised here; mypy will later complain
- # about the length mismatch in type-checking.
- elementwise_assignments = zip(rval.items, *[v.items for v in seq_lvals])
- for rv, *lvs in elementwise_assignments:
- self.process_module_assignment(lvs, rv, ctx)
- elif isinstance(rval, RefExpr):
- rnode = self.lookup_type_node(rval)
- if rnode and isinstance(rnode.node, MypyFile):
- for lval in lvals:
- if not isinstance(lval, RefExpr):
- continue
- # respect explicitly annotated type
- if isinstance(lval.node, Var) and lval.node.type is not None:
- continue
- # We can handle these assignments to locals and to self
- if isinstance(lval, NameExpr):
- lnode = self.current_symbol_table().get(lval.name)
- elif isinstance(lval, MemberExpr) and self.is_self_member_ref(lval):
- assert self.type is not None
- lnode = self.type.names.get(lval.name)
- else:
- continue
- if lnode:
- if isinstance(lnode.node, MypyFile) and lnode.node is not rnode.node:
- assert isinstance(lval, (NameExpr, MemberExpr))
- self.fail(
- 'Cannot assign multiple modules to name "{}" '
- 'without explicit "types.ModuleType" annotation'.format(lval.name),
- ctx,
- )
- # never create module alias except on initial var definition
- elif lval.is_inferred_def:
- assert rnode.node is not None
- lnode.node = rnode.node
- def process__all__(self, s: AssignmentStmt) -> None:
- """Export names if argument is a __all__ assignment."""
- if (
- len(s.lvalues) == 1
- and isinstance(s.lvalues[0], NameExpr)
- and s.lvalues[0].name == "__all__"
- and s.lvalues[0].kind == GDEF
- and isinstance(s.rvalue, (ListExpr, TupleExpr))
- ):
- self.add_exports(s.rvalue.items)
- def process__deletable__(self, s: AssignmentStmt) -> None:
- if not self.options.mypyc:
- return
- if (
- len(s.lvalues) == 1
- and isinstance(s.lvalues[0], NameExpr)
- and s.lvalues[0].name == "__deletable__"
- and s.lvalues[0].kind == MDEF
- ):
- rvalue = s.rvalue
- if not isinstance(rvalue, (ListExpr, TupleExpr)):
- self.fail('"__deletable__" must be initialized with a list or tuple expression', s)
- return
- items = rvalue.items
- attrs = []
- for item in items:
- if not isinstance(item, StrExpr):
- self.fail('Invalid "__deletable__" item; string literal expected', item)
- else:
- attrs.append(item.value)
- assert self.type
- self.type.deletable_attributes = attrs
- def process__slots__(self, s: AssignmentStmt) -> None:
- """
- Processing ``__slots__`` if defined in type.
- See: https://docs.python.org/3/reference/datamodel.html#slots
- """
- # Later we can support `__slots__` defined as `__slots__ = other = ('a', 'b')`
- if (
- isinstance(self.type, TypeInfo)
- and len(s.lvalues) == 1
- and isinstance(s.lvalues[0], NameExpr)
- and s.lvalues[0].name == "__slots__"
- and s.lvalues[0].kind == MDEF
- ):
- # We understand `__slots__` defined as string, tuple, list, set, and dict:
- if not isinstance(s.rvalue, (StrExpr, ListExpr, TupleExpr, SetExpr, DictExpr)):
- # For example, `__slots__` can be defined as a variable,
- # we don't support it for now.
- return
- if any(p.slots is None for p in self.type.mro[1:-1]):
- # At least one type in mro (excluding `self` and `object`)
- # does not have concrete `__slots__` defined. Ignoring.
- return
- concrete_slots = True
- rvalue: list[Expression] = []
- if isinstance(s.rvalue, StrExpr):
- rvalue.append(s.rvalue)
- elif isinstance(s.rvalue, (ListExpr, TupleExpr, SetExpr)):
- rvalue.extend(s.rvalue.items)
- else:
- # We have a special treatment of `dict` with possible `{**kwargs}` usage.
- # In this case we consider all `__slots__` to be non-concrete.
- for key, _ in s.rvalue.items:
- if concrete_slots and key is not None:
- rvalue.append(key)
- else:
- concrete_slots = False
- slots = []
- for item in rvalue:
- # Special case for `'__dict__'` value:
- # when specified it will still allow any attribute assignment.
- if isinstance(item, StrExpr) and item.value != "__dict__":
- slots.append(item.value)
- else:
- concrete_slots = False
- if not concrete_slots:
- # Some slot items are dynamic, we don't want any false positives,
- # so, we just pretend that this type does not have any slots at all.
- return
- # We need to copy all slots from super types:
- for super_type in self.type.mro[1:-1]:
- assert super_type.slots is not None
- slots.extend(super_type.slots)
- self.type.slots = set(slots)
- #
- # Misc statements
- #
- def visit_block(self, b: Block) -> None:
- if b.is_unreachable:
- return
- self.block_depth[-1] += 1
- for s in b.body:
- self.accept(s)
- self.block_depth[-1] -= 1
- def visit_block_maybe(self, b: Block | None) -> None:
- if b:
- self.visit_block(b)
- def visit_expression_stmt(self, s: ExpressionStmt) -> None:
- self.statement = s
- s.expr.accept(self)
- def visit_return_stmt(self, s: ReturnStmt) -> None:
- self.statement = s
- if not self.is_func_scope():
- self.fail('"return" outside function', s)
- if s.expr:
- s.expr.accept(self)
- def visit_raise_stmt(self, s: RaiseStmt) -> None:
- self.statement = s
- if s.expr:
- s.expr.accept(self)
- if s.from_expr:
- s.from_expr.accept(self)
- def visit_assert_stmt(self, s: AssertStmt) -> None:
- self.statement = s
- if s.expr:
- s.expr.accept(self)
- if s.msg:
- s.msg.accept(self)
- def visit_operator_assignment_stmt(self, s: OperatorAssignmentStmt) -> None:
- self.statement = s
- s.lvalue.accept(self)
- s.rvalue.accept(self)
- if (
- isinstance(s.lvalue, NameExpr)
- and s.lvalue.name == "__all__"
- and s.lvalue.kind == GDEF
- and isinstance(s.rvalue, (ListExpr, TupleExpr))
- ):
- self.add_exports(s.rvalue.items)
- def visit_while_stmt(self, s: WhileStmt) -> None:
- self.statement = s
- s.expr.accept(self)
- self.loop_depth[-1] += 1
- s.body.accept(self)
- self.loop_depth[-1] -= 1
- self.visit_block_maybe(s.else_body)
- def visit_for_stmt(self, s: ForStmt) -> None:
- if s.is_async:
- if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
- self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
- self.statement = s
- s.expr.accept(self)
- # Bind index variables and check if they define new names.
- self.analyze_lvalue(s.index, explicit_type=s.index_type is not None)
- if s.index_type:
- if self.is_classvar(s.index_type):
- self.fail_invalid_classvar(s.index)
- allow_tuple_literal = isinstance(s.index, TupleExpr)
- analyzed = self.anal_type(s.index_type, allow_tuple_literal=allow_tuple_literal)
- if analyzed is not None:
- self.store_declared_types(s.index, analyzed)
- s.index_type = analyzed
- self.loop_depth[-1] += 1
- self.visit_block(s.body)
- self.loop_depth[-1] -= 1
- self.visit_block_maybe(s.else_body)
- def visit_break_stmt(self, s: BreakStmt) -> None:
- self.statement = s
- if self.loop_depth[-1] == 0:
- self.fail('"break" outside loop', s, serious=True, blocker=True)
- def visit_continue_stmt(self, s: ContinueStmt) -> None:
- self.statement = s
- if self.loop_depth[-1] == 0:
- self.fail('"continue" outside loop', s, serious=True, blocker=True)
- def visit_if_stmt(self, s: IfStmt) -> None:
- self.statement = s
- infer_reachability_of_if_statement(s, self.options)
- for i in range(len(s.expr)):
- s.expr[i].accept(self)
- self.visit_block(s.body[i])
- self.visit_block_maybe(s.else_body)
- def visit_try_stmt(self, s: TryStmt) -> None:
- self.statement = s
- self.analyze_try_stmt(s, self)
- def analyze_try_stmt(self, s: TryStmt, visitor: NodeVisitor[None]) -> None:
- s.body.accept(visitor)
- for type, var, handler in zip(s.types, s.vars, s.handlers):
- if type:
- type.accept(visitor)
- if var:
- self.analyze_lvalue(var)
- handler.accept(visitor)
- if s.else_body:
- s.else_body.accept(visitor)
- if s.finally_body:
- s.finally_body.accept(visitor)
- def visit_with_stmt(self, s: WithStmt) -> None:
- self.statement = s
- types: list[Type] = []
- if s.is_async:
- if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
- self.fail(message_registry.ASYNC_WITH_OUTSIDE_COROUTINE, s, code=codes.SYNTAX)
- if s.unanalyzed_type:
- assert isinstance(s.unanalyzed_type, ProperType)
- actual_targets = [t for t in s.target if t is not None]
- if len(actual_targets) == 0:
- # We have a type for no targets
- self.fail('Invalid type comment: "with" statement has no targets', s)
- elif len(actual_targets) == 1:
- # We have one target and one type
- types = [s.unanalyzed_type]
- elif isinstance(s.unanalyzed_type, TupleType):
- # We have multiple targets and multiple types
- if len(actual_targets) == len(s.unanalyzed_type.items):
- types = s.unanalyzed_type.items.copy()
- else:
- # But it's the wrong number of items
- self.fail('Incompatible number of types for "with" targets', s)
- else:
- # We have multiple targets and one type
- self.fail('Multiple types expected for multiple "with" targets', s)
- new_types: list[Type] = []
- for e, n in zip(s.expr, s.target):
- e.accept(self)
- if n:
- self.analyze_lvalue(n, explicit_type=s.unanalyzed_type is not None)
- # Since we have a target, pop the next type from types
- if types:
- t = types.pop(0)
- if self.is_classvar(t):
- self.fail_invalid_classvar(n)
- allow_tuple_literal = isinstance(n, TupleExpr)
- analyzed = self.anal_type(t, allow_tuple_literal=allow_tuple_literal)
- if analyzed is not None:
- # TODO: Deal with this better
- new_types.append(analyzed)
- self.store_declared_types(n, analyzed)
- s.analyzed_types = new_types
- self.visit_block(s.body)
- def visit_del_stmt(self, s: DelStmt) -> None:
- self.statement = s
- s.expr.accept(self)
- if not self.is_valid_del_target(s.expr):
- self.fail("Invalid delete target", s)
- def is_valid_del_target(self, s: Expression) -> bool:
- if isinstance(s, (IndexExpr, NameExpr, MemberExpr)):
- return True
- elif isinstance(s, (TupleExpr, ListExpr)):
- return all(self.is_valid_del_target(item) for item in s.items)
- else:
- return False
- def visit_global_decl(self, g: GlobalDecl) -> None:
- self.statement = g
- for name in g.names:
- if name in self.nonlocal_decls[-1]:
- self.fail(f'Name "{name}" is nonlocal and global', g)
- self.global_decls[-1].add(name)
- def visit_nonlocal_decl(self, d: NonlocalDecl) -> None:
- self.statement = d
- if self.is_module_scope():
- self.fail("nonlocal declaration not allowed at module level", d)
- else:
- for name in d.names:
- for table in reversed(self.locals[:-1]):
- if table is not None and name in table:
- break
- else:
- self.fail(f'No binding for nonlocal "{name}" found', d)
- if self.locals[-1] is not None and name in self.locals[-1]:
- self.fail(
- 'Name "{}" is already defined in local '
- "scope before nonlocal declaration".format(name),
- d,
- )
- if name in self.global_decls[-1]:
- self.fail(f'Name "{name}" is nonlocal and global', d)
- self.nonlocal_decls[-1].add(name)
- def visit_match_stmt(self, s: MatchStmt) -> None:
- self.statement = s
- infer_reachability_of_match_statement(s, self.options)
- s.subject.accept(self)
- for i in range(len(s.patterns)):
- s.patterns[i].accept(self)
- guard = s.guards[i]
- if guard is not None:
- guard.accept(self)
- self.visit_block(s.bodies[i])
- #
- # Expressions
- #
- def visit_name_expr(self, expr: NameExpr) -> None:
- n = self.lookup(expr.name, expr)
- if n:
- self.bind_name_expr(expr, n)
- def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None:
- """Bind name expression to a symbol table node."""
- if isinstance(sym.node, TypeVarExpr) and self.tvar_scope.get_binding(sym):
- self.fail(
- '"{}" is a type variable and only valid in type ' "context".format(expr.name), expr
- )
- elif isinstance(sym.node, PlaceholderNode):
- self.process_placeholder(expr.name, "name", expr)
- else:
- expr.kind = sym.kind
- expr.node = sym.node
- expr.fullname = sym.fullname or ""
- def visit_super_expr(self, expr: SuperExpr) -> None:
- if not self.type and not expr.call.args:
- self.fail('"super" used outside class', expr)
- return
- expr.info = self.type
- for arg in expr.call.args:
- arg.accept(self)
- def visit_tuple_expr(self, expr: TupleExpr) -> None:
- for item in expr.items:
- if isinstance(item, StarExpr):
- item.valid = True
- item.accept(self)
- def visit_list_expr(self, expr: ListExpr) -> None:
- for item in expr.items:
- if isinstance(item, StarExpr):
- item.valid = True
- item.accept(self)
- def visit_set_expr(self, expr: SetExpr) -> None:
- for item in expr.items:
- if isinstance(item, StarExpr):
- item.valid = True
- item.accept(self)
- def visit_dict_expr(self, expr: DictExpr) -> None:
- for key, value in expr.items:
- if key is not None:
- key.accept(self)
- value.accept(self)
- def visit_star_expr(self, expr: StarExpr) -> None:
- if not expr.valid:
- self.fail("Can use starred expression only as assignment target", expr, blocker=True)
- else:
- expr.expr.accept(self)
- def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
- if not self.is_func_scope():
- self.fail('"yield from" outside function', e, serious=True, blocker=True)
- elif self.is_comprehension_stack[-1]:
- self.fail(
- '"yield from" inside comprehension or generator expression',
- e,
- serious=True,
- blocker=True,
- )
- elif self.function_stack[-1].is_coroutine:
- self.fail('"yield from" in async function', e, serious=True, blocker=True)
- else:
- self.function_stack[-1].is_generator = True
- if e.expr:
- e.expr.accept(self)
- def visit_call_expr(self, expr: CallExpr) -> None:
- """Analyze a call expression.
- Some call expressions are recognized as special forms, including
- cast(...).
- """
- expr.callee.accept(self)
- if refers_to_fullname(expr.callee, "typing.cast"):
- # Special form cast(...).
- if not self.check_fixed_args(expr, 2, "cast"):
- return
- # Translate first argument to an unanalyzed type.
- try:
- target = self.expr_to_unanalyzed_type(expr.args[0])
- except TypeTranslationError:
- self.fail("Cast target is not a type", expr)
- return
- # Piggyback CastExpr object to the CallExpr object; it takes
- # precedence over the CallExpr semantics.
- expr.analyzed = CastExpr(expr.args[1], target)
- expr.analyzed.line = expr.line
- expr.analyzed.column = expr.column
- expr.analyzed.accept(self)
- elif refers_to_fullname(expr.callee, ASSERT_TYPE_NAMES):
- if not self.check_fixed_args(expr, 2, "assert_type"):
- return
- # Translate second argument to an unanalyzed type.
- try:
- target = self.expr_to_unanalyzed_type(expr.args[1])
- except TypeTranslationError:
- self.fail("assert_type() type is not a type", expr)
- return
- expr.analyzed = AssertTypeExpr(expr.args[0], target)
- expr.analyzed.line = expr.line
- expr.analyzed.column = expr.column
- expr.analyzed.accept(self)
- elif refers_to_fullname(expr.callee, REVEAL_TYPE_NAMES):
- if not self.check_fixed_args(expr, 1, "reveal_type"):
- return
- expr.analyzed = RevealExpr(kind=REVEAL_TYPE, expr=expr.args[0])
- expr.analyzed.line = expr.line
- expr.analyzed.column = expr.column
- expr.analyzed.accept(self)
- elif refers_to_fullname(expr.callee, "builtins.reveal_locals"):
- # Store the local variable names into the RevealExpr for use in the
- # type checking pass
- local_nodes: list[Var] = []
- if self.is_module_scope():
- # try to determine just the variable declarations in module scope
- # self.globals.values() contains SymbolTableNode's
- # Each SymbolTableNode has an attribute node that is nodes.Var
- # look for variable nodes that marked as is_inferred
- # Each symboltable node has a Var node as .node
- local_nodes = [
- n.node
- for name, n in self.globals.items()
- if getattr(n.node, "is_inferred", False) and isinstance(n.node, Var)
- ]
- elif self.is_class_scope():
- # type = None # type: Optional[TypeInfo]
- if self.type is not None:
- local_nodes = [
- st.node for st in self.type.names.values() if isinstance(st.node, Var)
- ]
- elif self.is_func_scope():
- # locals = None # type: List[Optional[SymbolTable]]
- if self.locals is not None:
- symbol_table = self.locals[-1]
- if symbol_table is not None:
- local_nodes = [
- st.node for st in symbol_table.values() if isinstance(st.node, Var)
- ]
- expr.analyzed = RevealExpr(kind=REVEAL_LOCALS, local_nodes=local_nodes)
- expr.analyzed.line = expr.line
- expr.analyzed.column = expr.column
- expr.analyzed.accept(self)
- elif refers_to_fullname(expr.callee, "typing.Any"):
- # Special form Any(...) no longer supported.
- self.fail("Any(...) is no longer supported. Use cast(Any, ...) instead", expr)
- elif refers_to_fullname(expr.callee, "typing._promote"):
- # Special form _promote(...).
- if not self.check_fixed_args(expr, 1, "_promote"):
- return
- # Translate first argument to an unanalyzed type.
- try:
- target = self.expr_to_unanalyzed_type(expr.args[0])
- except TypeTranslationError:
- self.fail("Argument 1 to _promote is not a type", expr)
- return
- expr.analyzed = PromoteExpr(target)
- expr.analyzed.line = expr.line
- expr.analyzed.accept(self)
- elif refers_to_fullname(expr.callee, "builtins.dict"):
- expr.analyzed = self.translate_dict_call(expr)
- elif refers_to_fullname(expr.callee, "builtins.divmod"):
- if not self.check_fixed_args(expr, 2, "divmod"):
- return
- expr.analyzed = OpExpr("divmod", expr.args[0], expr.args[1])
- expr.analyzed.line = expr.line
- expr.analyzed.accept(self)
- else:
- # Normal call expression.
- for a in expr.args:
- a.accept(self)
- if (
- isinstance(expr.callee, MemberExpr)
- and isinstance(expr.callee.expr, NameExpr)
- and expr.callee.expr.name == "__all__"
- and expr.callee.expr.kind == GDEF
- and expr.callee.name in ("append", "extend", "remove")
- ):
- if expr.callee.name == "append" and expr.args:
- self.add_exports(expr.args[0])
- elif (
- expr.callee.name == "extend"
- and expr.args
- and isinstance(expr.args[0], (ListExpr, TupleExpr))
- ):
- self.add_exports(expr.args[0].items)
- elif (
- expr.callee.name == "remove"
- and expr.args
- and isinstance(expr.args[0], StrExpr)
- ):
- self.all_exports = [n for n in self.all_exports if n != expr.args[0].value]
- def translate_dict_call(self, call: CallExpr) -> DictExpr | None:
- """Translate 'dict(x=y, ...)' to {'x': y, ...} and 'dict()' to {}.
- For other variants of dict(...), return None.
- """
- if not all(kind in (ARG_NAMED, ARG_STAR2) for kind in call.arg_kinds):
- # Must still accept those args.
- for a in call.args:
- a.accept(self)
- return None
- expr = DictExpr(
- [
- (StrExpr(key) if key is not None else None, value)
- for key, value in zip(call.arg_names, call.args)
- ]
- )
- expr.set_line(call)
- expr.accept(self)
- return expr
- def check_fixed_args(self, expr: CallExpr, numargs: int, name: str) -> bool:
- """Verify that expr has specified number of positional args.
- Return True if the arguments are valid.
- """
- s = "s"
- if numargs == 1:
- s = ""
- if len(expr.args) != numargs:
- self.fail('"%s" expects %d argument%s' % (name, numargs, s), expr)
- return False
- if expr.arg_kinds != [ARG_POS] * numargs:
- self.fail(f'"{name}" must be called with {numargs} positional argument{s}', expr)
- return False
- return True
- def visit_member_expr(self, expr: MemberExpr) -> None:
- base = expr.expr
- base.accept(self)
- if isinstance(base, RefExpr) and isinstance(base.node, MypyFile):
- # Handle module attribute.
- sym = self.get_module_symbol(base.node, expr.name)
- if sym:
- if isinstance(sym.node, PlaceholderNode):
- self.process_placeholder(expr.name, "attribute", expr)
- return
- expr.kind = sym.kind
- expr.fullname = sym.fullname or ""
- expr.node = sym.node
- elif isinstance(base, RefExpr):
- # This branch handles the case C.bar (or cls.bar or self.bar inside
- # a classmethod/method), where C is a class and bar is a type
- # definition or a module resulting from `import bar` (or a module
- # assignment) inside class C. We look up bar in the class' TypeInfo
- # namespace. This is done only when bar is a module or a type;
- # other things (e.g. methods) are handled by other code in
- # checkmember.
- type_info = None
- if isinstance(base.node, TypeInfo):
- # C.bar where C is a class
- type_info = base.node
- elif isinstance(base.node, Var) and self.type and self.function_stack:
- # check for self.bar or cls.bar in method/classmethod
- func_def = self.function_stack[-1]
- if not func_def.is_static and isinstance(func_def.type, CallableType):
- formal_arg = func_def.type.argument_by_name(base.node.name)
- if formal_arg and formal_arg.pos == 0:
- type_info = self.type
- elif isinstance(base.node, TypeAlias) and base.node.no_args:
- assert isinstance(base.node.target, ProperType)
- if isinstance(base.node.target, Instance):
- type_info = base.node.target.type
- if type_info:
- n = type_info.names.get(expr.name)
- if n is not None and isinstance(n.node, (MypyFile, TypeInfo, TypeAlias)):
- if not n:
- return
- expr.kind = n.kind
- expr.fullname = n.fullname or ""
- expr.node = n.node
- def visit_op_expr(self, expr: OpExpr) -> None:
- expr.left.accept(self)
- if expr.op in ("and", "or"):
- inferred = infer_condition_value(expr.left, self.options)
- if (inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == "and") or (
- inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == "or"
- ):
- expr.right_unreachable = True
- return
- elif (inferred in (ALWAYS_TRUE, MYPY_TRUE) and expr.op == "and") or (
- inferred in (ALWAYS_FALSE, MYPY_FALSE) and expr.op == "or"
- ):
- expr.right_always = True
- expr.right.accept(self)
- def visit_comparison_expr(self, expr: ComparisonExpr) -> None:
- for operand in expr.operands:
- operand.accept(self)
- def visit_unary_expr(self, expr: UnaryExpr) -> None:
- expr.expr.accept(self)
- def visit_index_expr(self, expr: IndexExpr) -> None:
- base = expr.base
- base.accept(self)
- if (
- isinstance(base, RefExpr)
- and isinstance(base.node, TypeInfo)
- and not base.node.is_generic()
- ):
- expr.index.accept(self)
- elif (
- isinstance(base, RefExpr) and isinstance(base.node, TypeAlias)
- ) or refers_to_class_or_function(base):
- # We need to do full processing on every iteration, since some type
- # arguments may contain placeholder types.
- self.analyze_type_application(expr)
- else:
- expr.index.accept(self)
- def analyze_type_application(self, expr: IndexExpr) -> None:
- """Analyze special form -- type application (either direct or via type aliasing)."""
- types = self.analyze_type_application_args(expr)
- if types is None:
- return
- base = expr.base
- expr.analyzed = TypeApplication(base, types)
- expr.analyzed.line = expr.line
- expr.analyzed.column = expr.column
- # Types list, dict, set are not subscriptable, prohibit this if
- # subscripted either via type alias...
- if isinstance(base, RefExpr) and isinstance(base.node, TypeAlias):
- alias = base.node
- target = get_proper_type(alias.target)
- if isinstance(target, Instance):
- name = target.type.fullname
- if (
- alias.no_args
- and name # this avoids bogus errors for already reported aliases
- in get_nongen_builtins(self.options.python_version)
- and not self.is_stub_file
- and not alias.normalized
- ):
- self.fail(no_subscript_builtin_alias(name, propose_alt=False), expr)
- # ...or directly.
- else:
- n = self.lookup_type_node(base)
- if (
- n
- and n.fullname in get_nongen_builtins(self.options.python_version)
- and not self.is_stub_file
- ):
- self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr)
- def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:
- """Analyze type arguments (index) in a type application.
- Return None if anything was incomplete.
- """
- index = expr.index
- tag = self.track_incomplete_refs()
- self.analyze_type_expr(index)
- if self.found_incomplete_ref(tag):
- return None
- if self.basic_type_applications:
- # Postpone the rest until we have more information (for r.h.s. of an assignment)
- return None
- types: list[Type] = []
- if isinstance(index, TupleExpr):
- items = index.items
- is_tuple = isinstance(expr.base, RefExpr) and expr.base.fullname == "builtins.tuple"
- if is_tuple and len(items) == 2 and isinstance(items[-1], EllipsisExpr):
- items = items[:-1]
- else:
- items = [index]
- # whether param spec literals be allowed here
- # TODO: should this be computed once and passed in?
- # or is there a better way to do this?
- base = expr.base
- if isinstance(base, RefExpr) and isinstance(base.node, TypeAlias):
- alias = base.node
- target = get_proper_type(alias.target)
- if isinstance(target, Instance):
- has_param_spec = target.type.has_param_spec_type
- num_args = len(target.type.type_vars)
- else:
- has_param_spec = False
- num_args = -1
- elif isinstance(base, NameExpr) and isinstance(base.node, TypeInfo):
- has_param_spec = base.node.has_param_spec_type
- num_args = len(base.node.type_vars)
- else:
- has_param_spec = False
- num_args = -1
- for item in items:
- try:
- typearg = self.expr_to_unanalyzed_type(item)
- except TypeTranslationError:
- self.fail("Type expected within [...]", expr)
- return None
- analyzed = self.anal_type(
- typearg,
- # The type application may appear in base class expression,
- # where type variables are not bound yet. Or when accepting
- # r.h.s. of type alias before we figured out it is a type alias.
- allow_unbound_tvars=self.allow_unbound_tvars,
- allow_placeholder=True,
- allow_param_spec_literals=has_param_spec,
- )
- if analyzed is None:
- return None
- types.append(analyzed)
- if has_param_spec and num_args == 1 and types:
- first_arg = get_proper_type(types[0])
- if not (
- len(types) == 1 and isinstance(first_arg, (Parameters, ParamSpecType, AnyType))
- ):
- types = [Parameters(types, [ARG_POS] * len(types), [None] * len(types))]
- return types
- def visit_slice_expr(self, expr: SliceExpr) -> None:
- if expr.begin_index:
- expr.begin_index.accept(self)
- if expr.end_index:
- expr.end_index.accept(self)
- if expr.stride:
- expr.stride.accept(self)
- def visit_cast_expr(self, expr: CastExpr) -> None:
- expr.expr.accept(self)
- analyzed = self.anal_type(expr.type)
- if analyzed is not None:
- expr.type = analyzed
- def visit_assert_type_expr(self, expr: AssertTypeExpr) -> None:
- expr.expr.accept(self)
- analyzed = self.anal_type(expr.type)
- if analyzed is not None:
- expr.type = analyzed
- def visit_reveal_expr(self, expr: RevealExpr) -> None:
- if expr.kind == REVEAL_TYPE:
- if expr.expr is not None:
- expr.expr.accept(self)
- else:
- # Reveal locals doesn't have an inner expression, there's no
- # need to traverse inside it
- pass
- def visit_type_application(self, expr: TypeApplication) -> None:
- expr.expr.accept(self)
- for i in range(len(expr.types)):
- analyzed = self.anal_type(expr.types[i])
- if analyzed is not None:
- expr.types[i] = analyzed
- def visit_list_comprehension(self, expr: ListComprehension) -> None:
- if any(expr.generator.is_async):
- if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
- self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
- expr.generator.accept(self)
- def visit_set_comprehension(self, expr: SetComprehension) -> None:
- if any(expr.generator.is_async):
- if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
- self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
- expr.generator.accept(self)
- def visit_dictionary_comprehension(self, expr: DictionaryComprehension) -> None:
- if any(expr.is_async):
- if not self.is_func_scope() or not self.function_stack[-1].is_coroutine:
- self.fail(message_registry.ASYNC_FOR_OUTSIDE_COROUTINE, expr, code=codes.SYNTAX)
- with self.enter(expr):
- self.analyze_comp_for(expr)
- expr.key.accept(self)
- expr.value.accept(self)
- self.analyze_comp_for_2(expr)
- def visit_generator_expr(self, expr: GeneratorExpr) -> None:
- with self.enter(expr):
- self.analyze_comp_for(expr)
- expr.left_expr.accept(self)
- self.analyze_comp_for_2(expr)
- def analyze_comp_for(self, expr: GeneratorExpr | DictionaryComprehension) -> None:
- """Analyses the 'comp_for' part of comprehensions (part 1).
- That is the part after 'for' in (x for x in l if p). This analyzes
- variables and conditions which are analyzed in a local scope.
- """
- for i, (index, sequence, conditions) in enumerate(
- zip(expr.indices, expr.sequences, expr.condlists)
- ):
- if i > 0:
- sequence.accept(self)
- # Bind index variables.
- self.analyze_lvalue(index)
- for cond in conditions:
- cond.accept(self)
- def analyze_comp_for_2(self, expr: GeneratorExpr | DictionaryComprehension) -> None:
- """Analyses the 'comp_for' part of comprehensions (part 2).
- That is the part after 'for' in (x for x in l if p). This analyzes
- the 'l' part which is analyzed in the surrounding scope.
- """
- expr.sequences[0].accept(self)
- def visit_lambda_expr(self, expr: LambdaExpr) -> None:
- self.analyze_arg_initializers(expr)
- self.analyze_function_body(expr)
- def visit_conditional_expr(self, expr: ConditionalExpr) -> None:
- expr.if_expr.accept(self)
- expr.cond.accept(self)
- expr.else_expr.accept(self)
- def visit__promote_expr(self, expr: PromoteExpr) -> None:
- analyzed = self.anal_type(expr.type)
- if analyzed is not None:
- assert isinstance(analyzed, ProperType), "Cannot use type aliases for promotions"
- expr.type = analyzed
- def visit_yield_expr(self, e: YieldExpr) -> None:
- if not self.is_func_scope():
- self.fail('"yield" outside function', e, serious=True, blocker=True)
- elif self.is_comprehension_stack[-1]:
- self.fail(
- '"yield" inside comprehension or generator expression',
- e,
- serious=True,
- blocker=True,
- )
- elif self.function_stack[-1].is_coroutine:
- if self.options.python_version < (3, 6):
- self.fail('"yield" in async function', e, serious=True, blocker=True)
- else:
- self.function_stack[-1].is_generator = True
- self.function_stack[-1].is_async_generator = True
- else:
- self.function_stack[-1].is_generator = True
- if e.expr:
- e.expr.accept(self)
- def visit_await_expr(self, expr: AwaitExpr) -> None:
- if not self.is_func_scope() or not self.function_stack:
- # We check both because is_function_scope() returns True inside comprehensions.
- # This is not a blocker, because some enviroments (like ipython)
- # support top level awaits.
- self.fail('"await" outside function', expr, serious=True, code=codes.TOP_LEVEL_AWAIT)
- elif not self.function_stack[-1].is_coroutine:
- self.fail('"await" outside coroutine ("async def")', expr, serious=True, blocker=True)
- expr.expr.accept(self)
- #
- # Patterns
- #
- def visit_as_pattern(self, p: AsPattern) -> None:
- if p.pattern is not None:
- p.pattern.accept(self)
- if p.name is not None:
- self.analyze_lvalue(p.name)
- def visit_or_pattern(self, p: OrPattern) -> None:
- for pattern in p.patterns:
- pattern.accept(self)
- def visit_value_pattern(self, p: ValuePattern) -> None:
- p.expr.accept(self)
- def visit_sequence_pattern(self, p: SequencePattern) -> None:
- for pattern in p.patterns:
- pattern.accept(self)
- def visit_starred_pattern(self, p: StarredPattern) -> None:
- if p.capture is not None:
- self.analyze_lvalue(p.capture)
- def visit_mapping_pattern(self, p: MappingPattern) -> None:
- for key in p.keys:
- key.accept(self)
- for value in p.values:
- value.accept(self)
- if p.rest is not None:
- self.analyze_lvalue(p.rest)
- def visit_class_pattern(self, p: ClassPattern) -> None:
- p.class_ref.accept(self)
- for pos in p.positionals:
- pos.accept(self)
- for v in p.keyword_values:
- v.accept(self)
- #
- # Lookup functions
- #
- def lookup(
- self, name: str, ctx: Context, suppress_errors: bool = False
- ) -> SymbolTableNode | None:
- """Look up an unqualified (no dots) name in all active namespaces.
- Note that the result may contain a PlaceholderNode. The caller may
- want to defer in that case.
- Generate an error if the name is not defined unless suppress_errors
- is true or the current namespace is incomplete. In the latter case
- defer.
- """
- implicit_name = False
- # 1a. Name declared using 'global x' takes precedence
- if name in self.global_decls[-1]:
- if name in self.globals:
- return self.globals[name]
- if not suppress_errors:
- self.name_not_defined(name, ctx)
- return None
- # 1b. Name declared using 'nonlocal x' takes precedence
- if name in self.nonlocal_decls[-1]:
- for table in reversed(self.locals[:-1]):
- if table is not None and name in table:
- return table[name]
- if not suppress_errors:
- self.name_not_defined(name, ctx)
- return None
- # 2. Class attributes (if within class definition)
- if self.type and not self.is_func_scope() and name in self.type.names:
- node = self.type.names[name]
- if not node.implicit:
- if self.is_active_symbol_in_class_body(node.node):
- return node
- else:
- # Defined through self.x assignment
- implicit_name = True
- implicit_node = node
- # 3. Local (function) scopes
- for table in reversed(self.locals):
- if table is not None and name in table:
- return table[name]
- # 4. Current file global scope
- if name in self.globals:
- return self.globals[name]
- # 5. Builtins
- b = self.globals.get("__builtins__", None)
- if b:
- assert isinstance(b.node, MypyFile)
- table = b.node.names
- if name in table:
- if len(name) > 1 and name[0] == "_" and name[1] != "_":
- if not suppress_errors:
- self.name_not_defined(name, ctx)
- return None
- node = table[name]
- return node
- # Give up.
- if not implicit_name and not suppress_errors:
- self.name_not_defined(name, ctx)
- else:
- if implicit_name:
- return implicit_node
- return None
- def is_active_symbol_in_class_body(self, node: SymbolNode | None) -> bool:
- """Can a symbol defined in class body accessed at current statement?
- Only allow access to class attributes textually after
- the definition, so that it's possible to fall back to the
- outer scope. Example:
- class X: ...
- class C:
- X = X # Initializer refers to outer scope
- Nested classes are an exception, since we want to support
- arbitrary forward references in type annotations. Also, we
- allow forward references to type aliases to support recursive
- types.
- """
- # TODO: Forward reference to name imported in class body is not
- # caught.
- if self.statement is None:
- # Assume it's fine -- don't have enough context to check
- return True
- return (
- node is None
- or self.is_textually_before_statement(node)
- or not self.is_defined_in_current_module(node.fullname)
- or isinstance(node, (TypeInfo, TypeAlias))
- or (isinstance(node, PlaceholderNode) and node.becomes_typeinfo)
- )
- def is_textually_before_statement(self, node: SymbolNode) -> bool:
- """Check if a node is defined textually before the current statement
- Note that decorated functions' line number are the same as
- the top decorator.
- """
- assert self.statement
- line_diff = self.statement.line - node.line
- # The first branch handles reference an overloaded function variant inside itself,
- # this is a corner case where mypy technically deviates from runtime name resolution,
- # but it is fine because we want an overloaded function to be treated as a single unit.
- if self.is_overloaded_item(node, self.statement):
- return False
- elif isinstance(node, Decorator) and not node.is_overload:
- return line_diff > len(node.original_decorators)
- else:
- return line_diff > 0
- def is_overloaded_item(self, node: SymbolNode, statement: Statement) -> bool:
- """Check whether the function belongs to the overloaded variants"""
- if isinstance(node, OverloadedFuncDef) and isinstance(statement, FuncDef):
- in_items = statement in {
- item.func if isinstance(item, Decorator) else item for item in node.items
- }
- in_impl = node.impl is not None and (
- (isinstance(node.impl, Decorator) and statement is node.impl.func)
- or statement is node.impl
- )
- return in_items or in_impl
- return False
- def is_defined_in_current_module(self, fullname: str | None) -> bool:
- if not fullname:
- return False
- return module_prefix(self.modules, fullname) == self.cur_mod_id
- def lookup_qualified(
- self, name: str, ctx: Context, suppress_errors: bool = False
- ) -> SymbolTableNode | None:
- """Lookup a qualified name in all activate namespaces.
- Note that the result may contain a PlaceholderNode. The caller may
- want to defer in that case.
- Generate an error if the name is not defined unless suppress_errors
- is true or the current namespace is incomplete. In the latter case
- defer.
- """
- if "." not in name:
- # Simple case: look up a short name.
- return self.lookup(name, ctx, suppress_errors=suppress_errors)
- parts = name.split(".")
- namespace = self.cur_mod_id
- sym = self.lookup(parts[0], ctx, suppress_errors=suppress_errors)
- if sym:
- for i in range(1, len(parts)):
- node = sym.node
- part = parts[i]
- if isinstance(node, TypeInfo):
- nextsym = node.get(part)
- elif isinstance(node, MypyFile):
- nextsym = self.get_module_symbol(node, part)
- namespace = node.fullname
- elif isinstance(node, PlaceholderNode):
- return sym
- elif isinstance(node, TypeAlias) and node.no_args:
- assert isinstance(node.target, ProperType)
- if isinstance(node.target, Instance):
- nextsym = node.target.type.get(part)
- else:
- nextsym = None
- else:
- if isinstance(node, Var):
- typ = get_proper_type(node.type)
- if isinstance(typ, AnyType):
- # Allow access through Var with Any type without error.
- return self.implicit_symbol(sym, name, parts[i:], typ)
- # This might be something like valid `P.args` or invalid `P.__bound__` access.
- # Important note that `ParamSpecExpr` is also ignored in other places.
- # See https://github.com/python/mypy/pull/13468
- if isinstance(node, ParamSpecExpr) and part in ("args", "kwargs"):
- return None
- # Lookup through invalid node, such as variable or function
- nextsym = None
- if not nextsym or nextsym.module_hidden:
- if not suppress_errors:
- self.name_not_defined(name, ctx, namespace=namespace)
- return None
- sym = nextsym
- return sym
- def lookup_type_node(self, expr: Expression) -> SymbolTableNode | None:
- try:
- t = self.expr_to_unanalyzed_type(expr)
- except TypeTranslationError:
- return None
- if isinstance(t, UnboundType):
- n = self.lookup_qualified(t.name, expr, suppress_errors=True)
- return n
- return None
- def get_module_symbol(self, node: MypyFile, name: str) -> SymbolTableNode | None:
- """Look up a symbol from a module.
- Return None if no matching symbol could be bound.
- """
- module = node.fullname
- names = node.names
- sym = names.get(name)
- if not sym:
- fullname = module + "." + name
- if fullname in self.modules:
- sym = SymbolTableNode(GDEF, self.modules[fullname])
- elif self.is_incomplete_namespace(module):
- self.record_incomplete_ref()
- elif "__getattr__" in names and (
- node.is_stub or self.options.python_version >= (3, 7)
- ):
- gvar = self.create_getattr_var(names["__getattr__"], name, fullname)
- if gvar:
- sym = SymbolTableNode(GDEF, gvar)
- elif self.is_missing_module(fullname):
- # We use the fullname of the original definition so that we can
- # detect whether two names refer to the same thing.
- var_type = AnyType(TypeOfAny.from_unimported_type)
- v = Var(name, type=var_type)
- v._fullname = fullname
- sym = SymbolTableNode(GDEF, v)
- elif sym.module_hidden:
- sym = None
- return sym
- def is_missing_module(self, module: str) -> bool:
- return module in self.missing_modules
- def implicit_symbol(
- self, sym: SymbolTableNode, name: str, parts: list[str], source_type: AnyType
- ) -> SymbolTableNode:
- """Create symbol for a qualified name reference through Any type."""
- if sym.node is None:
- basename = None
- else:
- basename = sym.node.fullname
- if basename is None:
- fullname = name
- else:
- fullname = basename + "." + ".".join(parts)
- var_type = AnyType(TypeOfAny.from_another_any, source_type)
- var = Var(parts[-1], var_type)
- var._fullname = fullname
- return SymbolTableNode(GDEF, var)
- def create_getattr_var(
- self, getattr_defn: SymbolTableNode, name: str, fullname: str
- ) -> Var | None:
- """Create a dummy variable using module-level __getattr__ return type.
- If not possible, return None.
- Note that multiple Var nodes can be created for a single name. We
- can use the from_module_getattr and the fullname attributes to
- check if two dummy Var nodes refer to the same thing. Reusing Var
- nodes would require non-local mutable state, which we prefer to
- avoid.
- """
- if isinstance(getattr_defn.node, (FuncDef, Var)):
- node_type = get_proper_type(getattr_defn.node.type)
- if isinstance(node_type, CallableType):
- typ = node_type.ret_type
- else:
- typ = AnyType(TypeOfAny.from_error)
- v = Var(name, type=typ)
- v._fullname = fullname
- v.from_module_getattr = True
- return v
- return None
- def lookup_fully_qualified(self, fullname: str) -> SymbolTableNode:
- ret = self.lookup_fully_qualified_or_none(fullname)
- assert ret is not None, fullname
- return ret
- def lookup_fully_qualified_or_none(self, fullname: str) -> SymbolTableNode | None:
- """Lookup a fully qualified name that refers to a module-level definition.
- Don't assume that the name is defined. This happens in the global namespace --
- the local module namespace is ignored. This does not dereference indirect
- refs.
- Note that this can't be used for names nested in class namespaces.
- """
- # TODO: unify/clean-up/simplify lookup methods, see #4157.
- # TODO: support nested classes (but consider performance impact,
- # we might keep the module level only lookup for thing like 'builtins.int').
- assert "." in fullname
- module, name = fullname.rsplit(".", maxsplit=1)
- if module not in self.modules:
- return None
- filenode = self.modules[module]
- result = filenode.names.get(name)
- if result is None and self.is_incomplete_namespace(module):
- # TODO: More explicit handling of incomplete refs?
- self.record_incomplete_ref()
- return result
- def object_type(self) -> Instance:
- return self.named_type("builtins.object")
- def str_type(self) -> Instance:
- return self.named_type("builtins.str")
- def named_type(self, fullname: str, args: list[Type] | None = None) -> Instance:
- sym = self.lookup_fully_qualified(fullname)
- assert sym, "Internal error: attempted to construct unknown type"
- node = sym.node
- assert isinstance(node, TypeInfo)
- if args:
- # TODO: assert len(args) == len(node.defn.type_vars)
- return Instance(node, args)
- return Instance(node, [AnyType(TypeOfAny.special_form)] * len(node.defn.type_vars))
- def named_type_or_none(self, fullname: str, args: list[Type] | None = None) -> Instance | None:
- sym = self.lookup_fully_qualified_or_none(fullname)
- if not sym or isinstance(sym.node, PlaceholderNode):
- return None
- node = sym.node
- if isinstance(node, TypeAlias):
- assert isinstance(node.target, Instance) # type: ignore[misc]
- node = node.target.type
- assert isinstance(node, TypeInfo), node
- if args is not None:
- # TODO: assert len(args) == len(node.defn.type_vars)
- return Instance(node, args)
- return Instance(node, [AnyType(TypeOfAny.unannotated)] * len(node.defn.type_vars))
- def builtin_type(self, fully_qualified_name: str) -> Instance:
- """Legacy function -- use named_type() instead."""
- return self.named_type(fully_qualified_name)
- def lookup_current_scope(self, name: str) -> SymbolTableNode | None:
- if self.locals[-1] is not None:
- return self.locals[-1].get(name)
- elif self.type is not None:
- return self.type.names.get(name)
- else:
- return self.globals.get(name)
- #
- # Adding symbols
- #
- def add_symbol(
- self,
- name: str,
- node: SymbolNode,
- context: Context,
- module_public: bool = True,
- module_hidden: bool = False,
- can_defer: bool = True,
- escape_comprehensions: bool = False,
- ) -> bool:
- """Add symbol to the currently active symbol table.
- Generally additions to symbol table should go through this method or
- one of the methods below so that kinds, redefinitions, conditional
- definitions, and skipped names are handled consistently.
- Return True if we actually added the symbol, or False if we refused to do so
- (because something is not ready).
- If can_defer is True, defer current target if adding a placeholder.
- """
- if self.is_func_scope():
- kind = LDEF
- elif self.type is not None:
- kind = MDEF
- else:
- kind = GDEF
- symbol = SymbolTableNode(
- kind, node, module_public=module_public, module_hidden=module_hidden
- )
- return self.add_symbol_table_node(name, symbol, context, can_defer, escape_comprehensions)
- def add_symbol_skip_local(self, name: str, node: SymbolNode) -> None:
- """Same as above, but skipping the local namespace.
- This doesn't check for previous definition and is only used
- for serialization of method-level classes.
- Classes defined within methods can be exposed through an
- attribute type, but method-level symbol tables aren't serialized.
- This method can be used to add such classes to an enclosing,
- serialized symbol table.
- """
- # TODO: currently this is only used by named tuples and typed dicts.
- # Use this method also by normal classes, see issue #6422.
- if self.type is not None:
- names = self.type.names
- kind = MDEF
- else:
- names = self.globals
- kind = GDEF
- symbol = SymbolTableNode(kind, node)
- names[name] = symbol
- def add_symbol_table_node(
- self,
- name: str,
- symbol: SymbolTableNode,
- context: Context | None = None,
- can_defer: bool = True,
- escape_comprehensions: bool = False,
- ) -> bool:
- """Add symbol table node to the currently active symbol table.
- Return True if we actually added the symbol, or False if we refused
- to do so (because something is not ready or it was a no-op).
- Generate an error if there is an invalid redefinition.
- If context is None, unconditionally add node, since we can't report
- an error. Note that this is used by plugins to forcibly replace nodes!
- TODO: Prevent plugins from replacing nodes, as it could cause problems?
- Args:
- name: short name of symbol
- symbol: Node to add
- can_defer: if True, defer current target if adding a placeholder
- context: error context (see above about None value)
- """
- names = self.current_symbol_table(escape_comprehensions=escape_comprehensions)
- existing = names.get(name)
- if isinstance(symbol.node, PlaceholderNode) and can_defer:
- if context is not None:
- self.process_placeholder(name, "name", context)
- else:
- # see note in docstring describing None contexts
- self.defer()
- if (
- existing is not None
- and context is not None
- and not is_valid_replacement(existing, symbol)
- ):
- # There is an existing node, so this may be a redefinition.
- # If the new node points to the same node as the old one,
- # or if both old and new nodes are placeholders, we don't
- # need to do anything.
- old = existing.node
- new = symbol.node
- if isinstance(new, PlaceholderNode):
- # We don't know whether this is okay. Let's wait until the next iteration.
- return False
- if not is_same_symbol(old, new):
- if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
- self.add_redefinition(names, name, symbol)
- if not (isinstance(new, (FuncDef, Decorator)) and self.set_original_def(old, new)):
- self.name_already_defined(name, context, existing)
- elif name not in self.missing_names[-1] and "*" not in self.missing_names[-1]:
- names[name] = symbol
- self.progress = True
- return True
- return False
- def add_redefinition(self, names: SymbolTable, name: str, symbol: SymbolTableNode) -> None:
- """Add a symbol table node that reflects a redefinition as a function or a class.
- Redefinitions need to be added to the symbol table so that they can be found
- through AST traversal, but they have dummy names of form 'name-redefinition[N]',
- where N ranges over 2, 3, ... (omitted for the first redefinition).
- Note: we always store redefinitions independently of whether they are valid or not
- (so they will be semantically analyzed), the caller should give an error for invalid
- redefinitions (such as e.g. variable redefined as a class).
- """
- i = 1
- # Don't serialize redefined nodes. They are likely to have
- # busted internal references which can cause problems with
- # serialization and they can't have any external references to
- # them.
- symbol.no_serialize = True
- while True:
- if i == 1:
- new_name = f"{name}-redefinition"
- else:
- new_name = f"{name}-redefinition{i}"
- existing = names.get(new_name)
- if existing is None:
- names[new_name] = symbol
- return
- elif existing.node is symbol.node:
- # Already there
- return
- i += 1
- def add_local(self, node: Var | FuncDef | OverloadedFuncDef, context: Context) -> None:
- """Add local variable or function."""
- assert self.is_func_scope()
- name = node.name
- node._fullname = name
- self.add_symbol(name, node, context)
- def _get_node_for_class_scoped_import(
- self, name: str, symbol_node: SymbolNode | None, context: Context
- ) -> SymbolNode | None:
- if symbol_node is None:
- return None
- # I promise this type checks; I'm just making mypyc issues go away.
- # mypyc is absolutely convinced that `symbol_node` narrows to a Var in the following,
- # when it can also be a FuncBase. Once fixed, `f` in the following can be removed.
- # See also https://github.com/mypyc/mypyc/issues/892
- f: Callable[[object], Any] = lambda x: x
- if isinstance(f(symbol_node), (Decorator, FuncBase, Var)):
- # For imports in class scope, we construct a new node to represent the symbol and
- # set its `info` attribute to `self.type`.
- existing = self.current_symbol_table().get(name)
- if (
- # The redefinition checks in `add_symbol_table_node` don't work for our
- # constructed Var / FuncBase, so check for possible redefinitions here.
- existing is not None
- and isinstance(f(existing.node), (Decorator, FuncBase, Var))
- and (
- isinstance(f(existing.type), f(AnyType))
- or f(existing.type) == f(symbol_node).type
- )
- ):
- return existing.node
- # Construct the new node
- if isinstance(f(symbol_node), (FuncBase, Decorator)):
- # In theory we could construct a new node here as well, but in practice
- # it doesn't work well, see #12197
- typ: Type | None = AnyType(TypeOfAny.from_error)
- self.fail("Unsupported class scoped import", context)
- else:
- typ = f(symbol_node).type
- symbol_node = Var(name, typ)
- symbol_node._fullname = self.qualified_name(name)
- assert self.type is not None # guaranteed by is_class_scope
- symbol_node.info = self.type
- symbol_node.line = context.line
- symbol_node.column = context.column
- return symbol_node
- def add_imported_symbol(
- self,
- name: str,
- node: SymbolTableNode,
- context: ImportBase,
- module_public: bool,
- module_hidden: bool,
- ) -> None:
- """Add an alias to an existing symbol through import."""
- assert not module_hidden or not module_public
- existing_symbol = self.lookup_current_scope(name)
- if (
- existing_symbol
- and not isinstance(existing_symbol.node, PlaceholderNode)
- and not isinstance(node.node, PlaceholderNode)
- ):
- # Import can redefine a variable. They get special treatment.
- if self.process_import_over_existing_name(name, existing_symbol, node, context):
- return
- symbol_node: SymbolNode | None = node.node
- if self.is_class_scope():
- symbol_node = self._get_node_for_class_scoped_import(name, symbol_node, context)
- symbol = SymbolTableNode(
- node.kind, symbol_node, module_public=module_public, module_hidden=module_hidden
- )
- self.add_symbol_table_node(name, symbol, context)
- def add_unknown_imported_symbol(
- self,
- name: str,
- context: Context,
- target_name: str | None,
- module_public: bool,
- module_hidden: bool,
- ) -> None:
- """Add symbol that we don't know what it points to because resolving an import failed.
- This can happen if a module is missing, or it is present, but doesn't have
- the imported attribute. The `target_name` is the name of symbol in the namespace
- it is imported from. For example, for 'from mod import x as y' the target_name is
- 'mod.x'. This is currently used only to track logical dependencies.
- """
- existing = self.current_symbol_table().get(name)
- if existing and isinstance(existing.node, Var) and existing.node.is_suppressed_import:
- # This missing import was already added -- nothing to do here.
- return
- var = Var(name)
- if self.options.logical_deps and target_name is not None:
- # This makes it possible to add logical fine-grained dependencies
- # from a missing module. We can't use this by default, since in a
- # few places we assume that the full name points to a real
- # definition, but this name may point to nothing.
- var._fullname = target_name
- elif self.type:
- var._fullname = self.type.fullname + "." + name
- var.info = self.type
- else:
- var._fullname = self.qualified_name(name)
- var.is_ready = True
- any_type = AnyType(TypeOfAny.from_unimported_type, missing_import_name=var._fullname)
- var.type = any_type
- var.is_suppressed_import = True
- self.add_symbol(
- name, var, context, module_public=module_public, module_hidden=module_hidden
- )
- #
- # Other helpers
- #
- @contextmanager
- def tvar_scope_frame(self, frame: TypeVarLikeScope) -> Iterator[None]:
- old_scope = self.tvar_scope
- self.tvar_scope = frame
- yield
- self.tvar_scope = old_scope
- def defer(self, debug_context: Context | None = None, force_progress: bool = False) -> None:
- """Defer current analysis target to be analyzed again.
- This must be called if something in the current target is
- incomplete or has a placeholder node. However, this must *not*
- be called during the final analysis iteration! Instead, an error
- should be generated. Often 'process_placeholder' is a good
- way to either defer or generate an error.
- NOTE: Some methods, such as 'anal_type', 'mark_incomplete' and
- 'record_incomplete_ref', call this implicitly, or when needed.
- They are usually preferable to a direct defer() call.
- """
- assert not self.final_iteration, "Must not defer during final iteration"
- if force_progress:
- # Usually, we report progress if we have replaced a placeholder node
- # with an actual valid node. However, sometimes we need to update an
- # existing node *in-place*. For example, this is used by type aliases
- # in context of forward references and/or recursive aliases, and in
- # similar situations (recursive named tuples etc).
- self.progress = True
- self.deferred = True
- # Store debug info for this deferral.
- line = (
- debug_context.line if debug_context else self.statement.line if self.statement else -1
- )
- self.deferral_debug_context.append((self.cur_mod_id, line))
- def track_incomplete_refs(self) -> Tag:
- """Return tag that can be used for tracking references to incomplete names."""
- return self.num_incomplete_refs
- def found_incomplete_ref(self, tag: Tag) -> bool:
- """Have we encountered an incomplete reference since starting tracking?"""
- return self.num_incomplete_refs != tag
- def record_incomplete_ref(self) -> None:
- """Record the encounter of an incomplete reference and defer current analysis target."""
- self.defer()
- self.num_incomplete_refs += 1
- def mark_incomplete(
- self,
- name: str,
- node: Node,
- becomes_typeinfo: bool = False,
- module_public: bool = True,
- module_hidden: bool = False,
- ) -> None:
- """Mark a definition as incomplete (and defer current analysis target).
- Also potentially mark the current namespace as incomplete.
- Args:
- name: The name that we weren't able to define (or '*' if the name is unknown)
- node: The node that refers to the name (definition or lvalue)
- becomes_typeinfo: Pass this to PlaceholderNode (used by special forms like
- named tuples that will create TypeInfos).
- """
- self.defer(node)
- if name == "*":
- self.incomplete = True
- elif not self.is_global_or_nonlocal(name):
- fullname = self.qualified_name(name)
- assert self.statement
- placeholder = PlaceholderNode(
- fullname, node, self.statement.line, becomes_typeinfo=becomes_typeinfo
- )
- self.add_symbol(
- name,
- placeholder,
- module_public=module_public,
- module_hidden=module_hidden,
- context=dummy_context(),
- )
- self.missing_names[-1].add(name)
- def is_incomplete_namespace(self, fullname: str) -> bool:
- """Is a module or class namespace potentially missing some definitions?
- If a name is missing from an incomplete namespace, we'll need to defer the
- current analysis target.
- """
- return fullname in self.incomplete_namespaces
- def process_placeholder(
- self, name: str | None, kind: str, ctx: Context, force_progress: bool = False
- ) -> None:
- """Process a reference targeting placeholder node.
- If this is not a final iteration, defer current node,
- otherwise report an error.
- The 'kind' argument indicates if this a name or attribute expression
- (used for better error message).
- """
- if self.final_iteration:
- self.cannot_resolve_name(name, kind, ctx)
- else:
- self.defer(ctx, force_progress=force_progress)
- def cannot_resolve_name(self, name: str | None, kind: str, ctx: Context) -> None:
- name_format = f' "{name}"' if name else ""
- self.fail(f"Cannot resolve {kind}{name_format} (possible cyclic definition)", ctx)
- if not self.options.disable_recursive_aliases and self.is_func_scope():
- self.note("Recursive types are not allowed at function scope", ctx)
- def qualified_name(self, name: str) -> str:
- if self.type is not None:
- return self.type._fullname + "." + name
- elif self.is_func_scope():
- return name
- else:
- return self.cur_mod_id + "." + name
- @contextmanager
- def enter(
- self, function: FuncItem | GeneratorExpr | DictionaryComprehension
- ) -> Iterator[None]:
- """Enter a function, generator or comprehension scope."""
- names = self.saved_locals.setdefault(function, SymbolTable())
- self.locals.append(names)
- is_comprehension = isinstance(function, (GeneratorExpr, DictionaryComprehension))
- self.is_comprehension_stack.append(is_comprehension)
- self.global_decls.append(set())
- self.nonlocal_decls.append(set())
- # -1 since entering block will increment this to 0.
- self.block_depth.append(-1)
- self.loop_depth.append(0)
- self.missing_names.append(set())
- try:
- yield
- finally:
- self.locals.pop()
- self.is_comprehension_stack.pop()
- self.global_decls.pop()
- self.nonlocal_decls.pop()
- self.block_depth.pop()
- self.loop_depth.pop()
- self.missing_names.pop()
- def is_func_scope(self) -> bool:
- return self.locals[-1] is not None
- def is_nested_within_func_scope(self) -> bool:
- """Are we underneath a function scope, even if we are in a nested class also?"""
- return any(l is not None for l in self.locals)
- def is_class_scope(self) -> bool:
- return self.type is not None and not self.is_func_scope()
- def is_module_scope(self) -> bool:
- return not (self.is_class_scope() or self.is_func_scope())
- def current_symbol_kind(self) -> int:
- if self.is_class_scope():
- kind = MDEF
- elif self.is_func_scope():
- kind = LDEF
- else:
- kind = GDEF
- return kind
- def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTable:
- if self.is_func_scope():
- assert self.locals[-1] is not None
- if escape_comprehensions:
- assert len(self.locals) == len(self.is_comprehension_stack)
- # Retrieve the symbol table from the enclosing non-comprehension scope.
- for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)):
- if not is_comprehension:
- if i == len(self.locals) - 1: # The last iteration.
- # The caller of the comprehension is in the global space.
- names = self.globals
- else:
- names_candidate = self.locals[-1 - i]
- assert (
- names_candidate is not None
- ), "Escaping comprehension from invalid scope"
- names = names_candidate
- break
- else:
- assert False, "Should have at least one non-comprehension scope"
- else:
- names = self.locals[-1]
- assert names is not None
- elif self.type is not None:
- names = self.type.names
- else:
- names = self.globals
- return names
- def is_global_or_nonlocal(self, name: str) -> bool:
- return self.is_func_scope() and (
- name in self.global_decls[-1] or name in self.nonlocal_decls[-1]
- )
- def add_exports(self, exp_or_exps: Iterable[Expression] | Expression) -> None:
- exps = [exp_or_exps] if isinstance(exp_or_exps, Expression) else exp_or_exps
- for exp in exps:
- if isinstance(exp, StrExpr):
- self.all_exports.append(exp.value)
- def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None) -> None:
- incomplete = self.is_incomplete_namespace(namespace or self.cur_mod_id)
- if (
- namespace is None
- and self.type
- and not self.is_func_scope()
- and self.incomplete_type_stack[-1]
- and not self.final_iteration
- ):
- # We are processing a class body for the first time, so it is incomplete.
- incomplete = True
- if incomplete:
- # Target namespace is incomplete, so it's possible that the name will be defined
- # later on. Defer current target.
- self.record_incomplete_ref()
- return
- message = f'Name "{name}" is not defined'
- self.fail(message, ctx, code=codes.NAME_DEFINED)
- if f"builtins.{name}" in SUGGESTED_TEST_FIXTURES:
- # The user probably has a missing definition in a test fixture. Let's verify.
- fullname = f"builtins.{name}"
- if self.lookup_fully_qualified_or_none(fullname) is None:
- # Yes. Generate a helpful note.
- self.msg.add_fixture_note(fullname, ctx)
- modules_with_unimported_hints = {
- name.split(".", 1)[0] for name in TYPES_FOR_UNIMPORTED_HINTS
- }
- lowercased = {name.lower(): name for name in TYPES_FOR_UNIMPORTED_HINTS}
- for module in modules_with_unimported_hints:
- fullname = f"{module}.{name}".lower()
- if fullname not in lowercased:
- continue
- # User probably forgot to import these types.
- hint = (
- 'Did you forget to import it from "{module}"?'
- ' (Suggestion: "from {module} import {name}")'
- ).format(module=module, name=lowercased[fullname].rsplit(".", 1)[-1])
- self.note(hint, ctx, code=codes.NAME_DEFINED)
- def already_defined(
- self, name: str, ctx: Context, original_ctx: SymbolTableNode | SymbolNode | None, noun: str
- ) -> None:
- if isinstance(original_ctx, SymbolTableNode):
- node: SymbolNode | None = original_ctx.node
- elif isinstance(original_ctx, SymbolNode):
- node = original_ctx
- else:
- node = None
- if isinstance(original_ctx, SymbolTableNode) and isinstance(original_ctx.node, MypyFile):
- # Since this is an import, original_ctx.node points to the module definition.
- # Therefore its line number is always 1, which is not useful for this
- # error message.
- extra_msg = " (by an import)"
- elif node and node.line != -1 and self.is_local_name(node.fullname):
- # TODO: Using previous symbol node may give wrong line. We should use
- # the line number where the binding was established instead.
- extra_msg = f" on line {node.line}"
- else:
- extra_msg = " (possibly by an import)"
- self.fail(
- f'{noun} "{unmangle(name)}" already defined{extra_msg}', ctx, code=codes.NO_REDEF
- )
- def name_already_defined(
- self, name: str, ctx: Context, original_ctx: SymbolTableNode | SymbolNode | None = None
- ) -> None:
- self.already_defined(name, ctx, original_ctx, noun="Name")
- def attribute_already_defined(
- self, name: str, ctx: Context, original_ctx: SymbolTableNode | SymbolNode | None = None
- ) -> None:
- self.already_defined(name, ctx, original_ctx, noun="Attribute")
- def is_local_name(self, name: str) -> bool:
- """Does name look like reference to a definition in the current module?"""
- return self.is_defined_in_current_module(name) or "." not in name
- def in_checked_function(self) -> bool:
- """Should we type-check the current function?
- - Yes if --check-untyped-defs is set.
- - Yes outside functions.
- - Yes in annotated functions.
- - No otherwise.
- """
- if self.options.check_untyped_defs or not self.function_stack:
- return True
- current_index = len(self.function_stack) - 1
- while current_index >= 0:
- current_func = self.function_stack[current_index]
- if not isinstance(current_func, LambdaExpr):
- return not current_func.is_dynamic()
- # Special case, `lambda` inherits the "checked" state from its parent.
- # Because `lambda` itself cannot be annotated.
- # `lambdas` can be deeply nested, so we try to find at least one other parent.
- current_index -= 1
- # This means that we only have a stack of `lambda` functions,
- # no regular functions.
- return True
- def fail(
- self,
- msg: str,
- ctx: Context,
- serious: bool = False,
- *,
- code: ErrorCode | None = None,
- blocker: bool = False,
- ) -> None:
- if not serious and not self.in_checked_function():
- return
- # In case it's a bug and we don't really have context
- assert ctx is not None, msg
- self.errors.report(ctx.line, ctx.column, msg, blocker=blocker, code=code)
- def note(self, msg: str, ctx: Context, code: ErrorCode | None = None) -> None:
- if not self.in_checked_function():
- return
- self.errors.report(ctx.line, ctx.column, msg, severity="note", code=code)
- def incomplete_feature_enabled(self, feature: str, ctx: Context) -> bool:
- if feature not in self.options.enable_incomplete_feature:
- self.fail(
- f'"{feature}" support is experimental,'
- f" use --enable-incomplete-feature={feature} to enable",
- ctx,
- )
- return False
- return True
- def accept(self, node: Node) -> None:
- try:
- node.accept(self)
- except Exception as err:
- report_internal_error(err, self.errors.file, node.line, self.errors, self.options)
- def expr_to_analyzed_type(
- self,
- expr: Expression,
- report_invalid_types: bool = True,
- allow_placeholder: bool = False,
- allow_type_any: bool = False,
- allow_unbound_tvars: bool = False,
- allow_param_spec_literals: bool = False,
- ) -> Type | None:
- if isinstance(expr, CallExpr):
- # This is a legacy syntax intended mostly for Python 2, we keep it for
- # backwards compatibility, but new features like generic named tuples
- # and recursive named tuples will be not supported.
- expr.accept(self)
- internal_name, info, tvar_defs = self.named_tuple_analyzer.check_namedtuple(
- expr, None, self.is_func_scope()
- )
- if tvar_defs:
- self.fail("Generic named tuples are not supported for legacy class syntax", expr)
- self.note("Use either Python 3 class syntax, or the assignment syntax", expr)
- if internal_name is None:
- # Some form of namedtuple is the only valid type that looks like a call
- # expression. This isn't a valid type.
- raise TypeTranslationError()
- elif not info:
- self.defer(expr)
- return None
- assert info.tuple_type, "NamedTuple without tuple type"
- fallback = Instance(info, [])
- return TupleType(info.tuple_type.items, fallback=fallback)
- typ = self.expr_to_unanalyzed_type(expr)
- return self.anal_type(
- typ,
- report_invalid_types=report_invalid_types,
- allow_placeholder=allow_placeholder,
- allow_type_any=allow_type_any,
- allow_unbound_tvars=allow_unbound_tvars,
- allow_param_spec_literals=allow_param_spec_literals,
- )
- def analyze_type_expr(self, expr: Expression) -> None:
- # There are certain expressions that mypy does not need to semantically analyze,
- # since they analyzed solely as type. (For example, indexes in type alias definitions
- # and base classes in class defs). External consumers of the mypy AST may need
- # them semantically analyzed, however, if they need to treat it as an expression
- # and not a type. (Which is to say, mypyc needs to do this.) Do the analysis
- # in a fresh tvar scope in order to suppress any errors about using type variables.
- with self.tvar_scope_frame(TypeVarLikeScope()), self.allow_unbound_tvars_set():
- expr.accept(self)
- def type_analyzer(
- self,
- *,
- tvar_scope: TypeVarLikeScope | None = None,
- allow_tuple_literal: bool = False,
- allow_unbound_tvars: bool = False,
- allow_placeholder: bool = False,
- allow_required: bool = False,
- allow_param_spec_literals: bool = False,
- report_invalid_types: bool = True,
- prohibit_self_type: str | None = None,
- allow_type_any: bool = False,
- ) -> TypeAnalyser:
- if tvar_scope is None:
- tvar_scope = self.tvar_scope
- tpan = TypeAnalyser(
- self,
- tvar_scope,
- self.plugin,
- self.options,
- self.is_typeshed_stub_file,
- allow_unbound_tvars=allow_unbound_tvars,
- allow_tuple_literal=allow_tuple_literal,
- report_invalid_types=report_invalid_types,
- allow_placeholder=allow_placeholder,
- allow_required=allow_required,
- allow_param_spec_literals=allow_param_spec_literals,
- prohibit_self_type=prohibit_self_type,
- allow_type_any=allow_type_any,
- )
- tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic())
- tpan.global_scope = not self.type and not self.function_stack
- return tpan
- def expr_to_unanalyzed_type(self, node: Expression) -> ProperType:
- return expr_to_unanalyzed_type(node, self.options, self.is_stub_file)
- def anal_type(
- self,
- typ: Type,
- *,
- tvar_scope: TypeVarLikeScope | None = None,
- allow_tuple_literal: bool = False,
- allow_unbound_tvars: bool = False,
- allow_placeholder: bool = False,
- allow_required: bool = False,
- allow_param_spec_literals: bool = False,
- report_invalid_types: bool = True,
- prohibit_self_type: str | None = None,
- allow_type_any: bool = False,
- third_pass: bool = False,
- ) -> Type | None:
- """Semantically analyze a type.
- Args:
- typ: Type to analyze (if already analyzed, this is a no-op)
- allow_placeholder: If True, may return PlaceholderType if
- encountering an incomplete definition
- third_pass: Unused; only for compatibility with old semantic
- analyzer
- Return None only if some part of the type couldn't be bound *and* it
- referred to an incomplete namespace or definition. In this case also
- defer as needed. During a final iteration this won't return None;
- instead report an error if the type can't be analyzed and return
- AnyType.
- In case of other errors, report an error message and return AnyType.
- NOTE: The caller shouldn't defer even if this returns None or a
- placeholder type.
- """
- has_self_type = find_self_type(
- typ, lambda name: self.lookup_qualified(name, typ, suppress_errors=True)
- )
- if has_self_type and self.type and prohibit_self_type is None:
- self.setup_self_type()
- a = self.type_analyzer(
- tvar_scope=tvar_scope,
- allow_unbound_tvars=allow_unbound_tvars,
- allow_tuple_literal=allow_tuple_literal,
- allow_placeholder=allow_placeholder,
- allow_required=allow_required,
- allow_param_spec_literals=allow_param_spec_literals,
- report_invalid_types=report_invalid_types,
- prohibit_self_type=prohibit_self_type,
- allow_type_any=allow_type_any,
- )
- tag = self.track_incomplete_refs()
- typ = typ.accept(a)
- if self.found_incomplete_ref(tag):
- # Something could not be bound yet.
- return None
- self.add_type_alias_deps(a.aliases_used)
- return typ
- def class_type(self, self_type: Type) -> Type:
- return TypeType.make_normalized(self_type)
- def schedule_patch(self, priority: int, patch: Callable[[], None]) -> None:
- self.patches.append((priority, patch))
- def report_hang(self) -> None:
- print("Deferral trace:")
- for mod, line in self.deferral_debug_context:
- print(f" {mod}:{line}")
- self.errors.report(
- -1,
- -1,
- "INTERNAL ERROR: maximum semantic analysis iteration count reached",
- blocker=True,
- )
- def add_plugin_dependency(self, trigger: str, target: str | None = None) -> None:
- """Add dependency from trigger to a target.
- If the target is not given explicitly, use the current target.
- """
- if target is None:
- target = self.scope.current_target()
- self.cur_mod_node.plugin_deps.setdefault(trigger, set()).add(target)
- def add_type_alias_deps(
- self, aliases_used: Collection[str], target: str | None = None
- ) -> None:
- """Add full names of type aliases on which the current node depends.
- This is used by fine-grained incremental mode to re-check the corresponding nodes.
- If `target` is None, then the target node used will be the current scope.
- """
- if not aliases_used:
- # A basic optimization to avoid adding targets with no dependencies to
- # the `alias_deps` dict.
- return
- if target is None:
- target = self.scope.current_target()
- self.cur_mod_node.alias_deps[target].update(aliases_used)
- def is_mangled_global(self, name: str) -> bool:
- # A global is mangled if there exists at least one renamed variant.
- return unmangle(name) + "'" in self.globals
- def is_initial_mangled_global(self, name: str) -> bool:
- # If there are renamed definitions for a global, the first one has exactly one prime.
- return name == unmangle(name) + "'"
- def parse_bool(self, expr: Expression) -> bool | None:
- # This wrapper is preserved for plugins.
- return parse_bool(expr)
- def parse_str_literal(self, expr: Expression) -> str | None:
- """Attempt to find the string literal value of the given expression. Returns `None` if no
- literal value can be found."""
- if isinstance(expr, StrExpr):
- return expr.value
- if isinstance(expr, RefExpr) and isinstance(expr.node, Var) and expr.node.type is not None:
- values = try_getting_str_literals_from_type(expr.node.type)
- if values is not None and len(values) == 1:
- return values[0]
- return None
- def set_future_import_flags(self, module_name: str) -> None:
- if module_name in FUTURE_IMPORTS:
- self.modules[self.cur_mod_id].future_import_flags.add(FUTURE_IMPORTS[module_name])
- def is_future_flag_set(self, flag: str) -> bool:
- return self.modules[self.cur_mod_id].is_future_flag_set(flag)
- def parse_dataclass_transform_spec(self, call: CallExpr) -> DataclassTransformSpec:
- """Build a DataclassTransformSpec from the arguments passed to the given call to
- typing.dataclass_transform."""
- parameters = DataclassTransformSpec()
- for name, value in zip(call.arg_names, call.args):
- # Skip any positional args. Note that any such args are invalid, but we can rely on
- # typeshed to enforce this and don't need an additional error here.
- if name is None:
- continue
- # field_specifiers is currently the only non-boolean argument; check for it first so
- # so the rest of the block can fail through to handling booleans
- if name == "field_specifiers":
- parameters.field_specifiers = self.parse_dataclass_transform_field_specifiers(
- value
- )
- continue
- boolean = require_bool_literal_argument(self, value, name)
- if boolean is None:
- continue
- if name == "eq_default":
- parameters.eq_default = boolean
- elif name == "order_default":
- parameters.order_default = boolean
- elif name == "kw_only_default":
- parameters.kw_only_default = boolean
- elif name == "frozen_default":
- parameters.frozen_default = boolean
- else:
- self.fail(f'Unrecognized dataclass_transform parameter "{name}"', call)
- return parameters
- def parse_dataclass_transform_field_specifiers(self, arg: Expression) -> tuple[str, ...]:
- if not isinstance(arg, TupleExpr):
- self.fail('"field_specifiers" argument must be a tuple literal', arg)
- return tuple()
- names = []
- for specifier in arg.items:
- if not isinstance(specifier, RefExpr):
- self.fail('"field_specifiers" must only contain identifiers', specifier)
- return tuple()
- names.append(specifier.fullname)
- return tuple(names)
- def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike:
- if isinstance(sig, CallableType):
- if len(sig.arg_types) == 0:
- return sig
- return sig.copy_modified(arg_types=[new] + sig.arg_types[1:])
- elif isinstance(sig, Overloaded):
- return Overloaded(
- [cast(CallableType, replace_implicit_first_type(i, new)) for i in sig.items]
- )
- else:
- assert False
- def refers_to_fullname(node: Expression, fullnames: str | tuple[str, ...]) -> bool:
- """Is node a name or member expression with the given full name?"""
- if not isinstance(fullnames, tuple):
- fullnames = (fullnames,)
- if not isinstance(node, RefExpr):
- return False
- if node.fullname in fullnames:
- return True
- if isinstance(node.node, TypeAlias):
- return is_named_instance(node.node.target, fullnames)
- return False
- def refers_to_class_or_function(node: Expression) -> bool:
- """Does semantically analyzed node refer to a class?"""
- return isinstance(node, RefExpr) and isinstance(
- node.node, (TypeInfo, FuncDef, OverloadedFuncDef)
- )
- def find_duplicate(list: list[T]) -> T | None:
- """If the list has duplicates, return one of the duplicates.
- Otherwise, return None.
- """
- for i in range(1, len(list)):
- if list[i] in list[:i]:
- return list[i]
- return None
- def remove_imported_names_from_symtable(names: SymbolTable, module: str) -> None:
- """Remove all imported names from the symbol table of a module."""
- removed: list[str] = []
- for name, node in names.items():
- if node.node is None:
- continue
- fullname = node.node.fullname
- prefix = fullname[: fullname.rfind(".")]
- if prefix != module:
- removed.append(name)
- for name in removed:
- del names[name]
- def make_any_non_explicit(t: Type) -> Type:
- """Replace all Any types within in with Any that has attribute 'explicit' set to False"""
- return t.accept(MakeAnyNonExplicit())
- class MakeAnyNonExplicit(TrivialSyntheticTypeTranslator):
- def visit_any(self, t: AnyType) -> Type:
- if t.type_of_any == TypeOfAny.explicit:
- return t.copy_modified(TypeOfAny.special_form)
- return t
- def visit_type_alias_type(self, t: TypeAliasType) -> Type:
- return t.copy_modified(args=[a.accept(self) for a in t.args])
- def apply_semantic_analyzer_patches(patches: list[tuple[int, Callable[[], None]]]) -> None:
- """Call patch callbacks in the right order.
- This should happen after semantic analyzer pass 3.
- """
- patches_by_priority = sorted(patches, key=lambda x: x[0])
- for priority, patch_func in patches_by_priority:
- patch_func()
- def names_modified_by_assignment(s: AssignmentStmt) -> list[NameExpr]:
- """Return all unqualified (short) names assigned to in an assignment statement."""
- result: list[NameExpr] = []
- for lvalue in s.lvalues:
- result += names_modified_in_lvalue(lvalue)
- return result
- def names_modified_in_lvalue(lvalue: Lvalue) -> list[NameExpr]:
- """Return all NameExpr assignment targets in an Lvalue."""
- if isinstance(lvalue, NameExpr):
- return [lvalue]
- elif isinstance(lvalue, StarExpr):
- return names_modified_in_lvalue(lvalue.expr)
- elif isinstance(lvalue, (ListExpr, TupleExpr)):
- result: list[NameExpr] = []
- for item in lvalue.items:
- result += names_modified_in_lvalue(item)
- return result
- return []
- def is_same_var_from_getattr(n1: SymbolNode | None, n2: SymbolNode | None) -> bool:
- """Do n1 and n2 refer to the same Var derived from module-level __getattr__?"""
- return (
- isinstance(n1, Var)
- and n1.from_module_getattr
- and isinstance(n2, Var)
- and n2.from_module_getattr
- and n1.fullname == n2.fullname
- )
- def dummy_context() -> Context:
- return TempNode(AnyType(TypeOfAny.special_form))
- def is_valid_replacement(old: SymbolTableNode, new: SymbolTableNode) -> bool:
- """Can symbol table node replace an existing one?
- These are the only valid cases:
- 1. Placeholder gets replaced with a non-placeholder
- 2. Placeholder that isn't known to become type replaced with a
- placeholder that can become a type
- """
- if isinstance(old.node, PlaceholderNode):
- if isinstance(new.node, PlaceholderNode):
- return not old.node.becomes_typeinfo and new.node.becomes_typeinfo
- else:
- return True
- return False
- def is_same_symbol(a: SymbolNode | None, b: SymbolNode | None) -> bool:
- return (
- a == b
- or (isinstance(a, PlaceholderNode) and isinstance(b, PlaceholderNode))
- or is_same_var_from_getattr(a, b)
- )
- def is_trivial_body(block: Block) -> bool:
- """Returns 'true' if the given body is "trivial" -- if it contains just a "pass",
- "..." (ellipsis), or "raise NotImplementedError()". A trivial body may also
- start with a statement containing just a string (e.g. a docstring).
- Note: Functions that raise other kinds of exceptions do not count as
- "trivial". We use this function to help us determine when it's ok to
- relax certain checks on body, but functions that raise arbitrary exceptions
- are more likely to do non-trivial work. For example:
- def halt(self, reason: str = ...) -> NoReturn:
- raise MyCustomError("Fatal error: " + reason, self.line, self.context)
- A function that raises just NotImplementedError is much less likely to be
- this complex.
- Note: If you update this, you may also need to update
- mypy.fastparse.is_possible_trivial_body!
- """
- body = block.body
- if not body:
- # Functions have empty bodies only if the body is stripped or the function is
- # generated or deserialized. In these cases the body is unknown.
- return False
- # Skip a docstring
- if isinstance(body[0], ExpressionStmt) and isinstance(body[0].expr, StrExpr):
- body = block.body[1:]
- if len(body) == 0:
- # There's only a docstring (or no body at all).
- return True
- elif len(body) > 1:
- return False
- stmt = body[0]
- if isinstance(stmt, RaiseStmt):
- expr = stmt.expr
- if expr is None:
- return False
- if isinstance(expr, CallExpr):
- expr = expr.callee
- return isinstance(expr, NameExpr) and expr.fullname == "builtins.NotImplementedError"
- return isinstance(stmt, PassStmt) or (
- isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, EllipsisExpr)
- )
|