diff --git a/crates/ty_python_semantic/resources/mdtest/call/union.md b/crates/ty_python_semantic/resources/mdtest/call/union.md index 09943c0801..c944a49cb7 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/union.md +++ b/crates/ty_python_semantic/resources/mdtest/call/union.md @@ -279,6 +279,521 @@ class RecursiveAttr3: reveal_type(RecursiveAttr3().i) # revealed: Unknown | int ``` +We set a much higher limit for non-recursive unions of enum literals, because huge enums are common +in generated code and it becomes frustrating if reachability analysis fails when matching over these +enums: + +```py +from enum import Enum +from ty_extensions import Intersection, Not + +class Huge(Enum): + OPTION0 = "0" + OPTION1 = "1" + OPTION2 = "2" + OPTION3 = "3" + OPTION4 = "4" + OPTION5 = "5" + OPTION6 = "6" + OPTION7 = "7" + OPTION8 = "8" + OPTION9 = "9" + OPTION10 = "10" + OPTION11 = "11" + OPTION12 = "12" + OPTION13 = "13" + OPTION14 = "14" + OPTION15 = "15" + OPTION16 = "16" + OPTION17 = "17" + OPTION18 = "18" + OPTION19 = "19" + OPTION20 = "20" + OPTION21 = "21" + OPTION22 = "22" + OPTION23 = "23" + OPTION24 = "24" + OPTION25 = "25" + OPTION26 = "26" + OPTION27 = "27" + OPTION28 = "28" + OPTION29 = "29" + OPTION30 = "30" + OPTION31 = "31" + OPTION32 = "32" + OPTION33 = "33" + OPTION34 = "34" + OPTION35 = "35" + OPTION36 = "36" + OPTION37 = "37" + OPTION38 = "38" + OPTION39 = "39" + OPTION40 = "40" + OPTION41 = "41" + OPTION42 = "42" + OPTION43 = "43" + OPTION44 = "44" + OPTION45 = "45" + OPTION46 = "46" + OPTION47 = "47" + OPTION48 = "48" + OPTION49 = "49" + OPTION50 = "50" + OPTION51 = "51" + OPTION52 = "52" + OPTION53 = "53" + OPTION54 = "54" + OPTION55 = "55" + OPTION56 = "56" + OPTION57 = "57" + OPTION58 = "58" + OPTION59 = "59" + OPTION60 = "60" + OPTION61 = "61" + OPTION62 = "62" + OPTION63 = "63" + OPTION64 = "64" + OPTION65 = "65" + OPTION66 = "66" + OPTION67 = "67" + OPTION68 = "68" + OPTION69 = "69" + OPTION70 = "70" + OPTION71 = "71" + OPTION72 = "72" + OPTION73 = "73" + OPTION74 = "74" + OPTION75 = "75" + OPTION76 = "76" + OPTION77 = "77" + OPTION78 = "78" + OPTION79 = "79" + OPTION80 = "80" + OPTION81 = "81" + OPTION82 = "82" + OPTION83 = "83" + OPTION84 = "84" + OPTION85 = "85" + OPTION86 = "86" + OPTION87 = "87" + OPTION88 = "88" + OPTION89 = "89" + OPTION90 = "90" + OPTION91 = "91" + OPTION92 = "92" + OPTION93 = "93" + OPTION94 = "94" + OPTION95 = "95" + OPTION96 = "96" + OPTION97 = "97" + OPTION98 = "98" + OPTION99 = "99" + OPTION100 = "100" + OPTION101 = "101" + OPTION102 = "102" + OPTION103 = "103" + OPTION104 = "104" + OPTION105 = "105" + OPTION106 = "106" + OPTION107 = "107" + OPTION108 = "108" + OPTION109 = "109" + OPTION110 = "110" + OPTION111 = "111" + OPTION112 = "112" + OPTION113 = "113" + OPTION114 = "114" + OPTION115 = "115" + OPTION116 = "116" + OPTION117 = "117" + OPTION118 = "118" + OPTION119 = "119" + OPTION120 = "120" + OPTION121 = "121" + OPTION122 = "122" + OPTION123 = "123" + OPTION124 = "124" + OPTION125 = "125" + OPTION126 = "126" + OPTION127 = "127" + OPTION128 = "128" + OPTION129 = "129" + OPTION130 = "130" + OPTION131 = "131" + OPTION132 = "132" + OPTION133 = "133" + OPTION134 = "134" + OPTION135 = "135" + OPTION136 = "136" + OPTION137 = "137" + OPTION138 = "138" + OPTION139 = "139" + OPTION140 = "140" + OPTION141 = "141" + OPTION142 = "142" + OPTION143 = "143" + OPTION144 = "144" + OPTION145 = "145" + OPTION146 = "146" + OPTION147 = "147" + OPTION148 = "148" + OPTION149 = "149" + OPTION150 = "150" + OPTION151 = "151" + OPTION152 = "152" + OPTION153 = "153" + OPTION154 = "154" + OPTION155 = "155" + OPTION156 = "156" + OPTION157 = "157" + OPTION158 = "158" + OPTION159 = "159" + OPTION160 = "160" + OPTION161 = "161" + OPTION162 = "162" + OPTION163 = "163" + OPTION164 = "164" + OPTION165 = "165" + OPTION166 = "166" + OPTION167 = "167" + OPTION168 = "168" + OPTION169 = "169" + OPTION170 = "170" + OPTION171 = "171" + OPTION172 = "172" + OPTION173 = "173" + OPTION174 = "174" + OPTION175 = "175" + OPTION176 = "176" + OPTION177 = "177" + OPTION178 = "178" + OPTION179 = "179" + OPTION180 = "180" + OPTION181 = "181" + OPTION182 = "182" + OPTION183 = "183" + OPTION184 = "184" + OPTION185 = "185" + OPTION186 = "186" + OPTION187 = "187" + OPTION188 = "188" + OPTION189 = "189" + OPTION190 = "190" + OPTION191 = "191" + OPTION192 = "192" + OPTION193 = "193" + OPTION194 = "194" + OPTION195 = "195" + OPTION196 = "196" + OPTION197 = "197" + OPTION198 = "198" + OPTION199 = "199" + OPTION200 = "200" + OPTION201 = "201" + OPTION202 = "202" + OPTION203 = "203" + OPTION204 = "204" + OPTION205 = "205" + OPTION206 = "206" + OPTION207 = "207" + OPTION208 = "208" + OPTION209 = "209" + OPTION210 = "210" + OPTION211 = "211" + OPTION212 = "212" + OPTION213 = "213" + OPTION214 = "214" + OPTION215 = "215" + OPTION216 = "216" + OPTION217 = "217" + OPTION218 = "218" + OPTION219 = "219" + OPTION220 = "220" + OPTION221 = "221" + OPTION222 = "222" + OPTION223 = "223" + OPTION224 = "224" + OPTION225 = "225" + OPTION226 = "226" + OPTION227 = "227" + OPTION228 = "228" + OPTION229 = "229" + OPTION230 = "230" + OPTION231 = "231" + OPTION232 = "232" + OPTION233 = "233" + OPTION234 = "234" + OPTION235 = "235" + OPTION236 = "236" + OPTION237 = "237" + OPTION238 = "238" + OPTION239 = "239" + OPTION240 = "240" + OPTION241 = "241" + OPTION242 = "242" + OPTION243 = "243" + OPTION244 = "244" + OPTION245 = "245" + OPTION246 = "246" + OPTION247 = "247" + OPTION248 = "248" + OPTION249 = "249" + OPTION250 = "250" + OPTION251 = "251" + OPTION252 = "252" + OPTION253 = "253" + OPTION254 = "254" + OPTION255 = "255" + OPTION256 = "256" + OPTION257 = "257" + OPTION258 = "258" + OPTION259 = "259" + OPTION260 = "260" + OPTION261 = "261" + OPTION262 = "262" + OPTION263 = "263" + OPTION264 = "264" + OPTION265 = "265" + OPTION266 = "266" + OPTION267 = "267" + OPTION268 = "268" + OPTION269 = "269" + OPTION270 = "270" + OPTION271 = "271" + OPTION272 = "272" + OPTION273 = "273" + OPTION274 = "274" + OPTION275 = "275" + OPTION276 = "276" + OPTION277 = "277" + OPTION278 = "278" + OPTION279 = "279" + OPTION280 = "280" + OPTION281 = "281" + OPTION282 = "282" + OPTION283 = "283" + OPTION284 = "284" + OPTION285 = "285" + OPTION286 = "286" + OPTION287 = "287" + OPTION288 = "288" + OPTION289 = "289" + OPTION290 = "290" + OPTION291 = "291" + OPTION292 = "292" + OPTION293 = "293" + OPTION294 = "294" + OPTION295 = "295" + OPTION296 = "296" + OPTION297 = "297" + OPTION298 = "298" + OPTION299 = "299" + OPTION300 = "300" + OPTION301 = "301" + OPTION302 = "302" + OPTION303 = "303" + OPTION304 = "304" + OPTION305 = "305" + OPTION306 = "306" + OPTION307 = "307" + OPTION308 = "308" + OPTION309 = "309" + OPTION310 = "310" + OPTION311 = "311" + OPTION312 = "312" + OPTION313 = "313" + OPTION314 = "314" + OPTION315 = "315" + OPTION316 = "316" + OPTION317 = "317" + OPTION318 = "318" + OPTION319 = "319" + OPTION320 = "320" + OPTION321 = "321" + OPTION322 = "322" + OPTION323 = "323" + OPTION324 = "324" + OPTION325 = "325" + OPTION326 = "326" + OPTION327 = "327" + OPTION328 = "328" + OPTION329 = "329" + OPTION330 = "330" + OPTION331 = "331" + OPTION332 = "332" + OPTION333 = "333" + OPTION334 = "334" + OPTION335 = "335" + OPTION336 = "336" + OPTION337 = "337" + OPTION338 = "338" + OPTION339 = "339" + OPTION340 = "340" + OPTION341 = "341" + OPTION342 = "342" + OPTION343 = "343" + OPTION344 = "344" + OPTION345 = "345" + OPTION346 = "346" + OPTION347 = "347" + OPTION348 = "348" + OPTION349 = "349" + OPTION350 = "350" + OPTION351 = "351" + OPTION352 = "352" + OPTION353 = "353" + OPTION354 = "354" + OPTION355 = "355" + OPTION356 = "356" + OPTION357 = "357" + OPTION358 = "358" + OPTION359 = "359" + OPTION360 = "360" + OPTION361 = "361" + OPTION362 = "362" + OPTION363 = "363" + OPTION364 = "364" + OPTION365 = "365" + OPTION366 = "366" + OPTION367 = "367" + OPTION368 = "368" + OPTION369 = "369" + OPTION370 = "370" + OPTION371 = "371" + OPTION372 = "372" + OPTION373 = "373" + OPTION374 = "374" + OPTION375 = "375" + OPTION376 = "376" + OPTION377 = "377" + OPTION378 = "378" + OPTION379 = "379" + OPTION380 = "380" + OPTION381 = "381" + OPTION382 = "382" + OPTION383 = "383" + OPTION384 = "384" + OPTION385 = "385" + OPTION386 = "386" + OPTION387 = "387" + OPTION388 = "388" + OPTION389 = "389" + OPTION390 = "390" + OPTION391 = "391" + OPTION392 = "392" + OPTION393 = "393" + OPTION394 = "394" + OPTION395 = "395" + OPTION396 = "396" + OPTION397 = "397" + OPTION398 = "398" + OPTION399 = "399" + OPTION400 = "400" + OPTION401 = "401" + OPTION402 = "402" + OPTION403 = "403" + OPTION404 = "404" + OPTION405 = "405" + OPTION406 = "406" + OPTION407 = "407" + OPTION408 = "408" + OPTION409 = "409" + OPTION410 = "410" + OPTION411 = "411" + OPTION412 = "412" + OPTION413 = "413" + OPTION414 = "414" + OPTION415 = "415" + OPTION416 = "416" + OPTION417 = "417" + OPTION418 = "418" + OPTION419 = "419" + OPTION420 = "420" + OPTION421 = "421" + OPTION422 = "422" + OPTION423 = "423" + OPTION424 = "424" + OPTION425 = "425" + OPTION426 = "426" + OPTION427 = "427" + OPTION428 = "428" + OPTION429 = "429" + OPTION430 = "430" + OPTION431 = "431" + OPTION432 = "432" + OPTION433 = "433" + OPTION434 = "434" + OPTION435 = "435" + OPTION436 = "436" + OPTION437 = "437" + OPTION438 = "438" + OPTION439 = "439" + OPTION440 = "440" + OPTION441 = "441" + OPTION442 = "442" + OPTION443 = "443" + OPTION444 = "444" + OPTION445 = "445" + OPTION446 = "446" + OPTION447 = "447" + OPTION448 = "448" + OPTION449 = "449" + OPTION450 = "450" + OPTION451 = "451" + OPTION452 = "452" + OPTION453 = "453" + OPTION454 = "454" + OPTION455 = "455" + OPTION456 = "456" + OPTION457 = "457" + OPTION458 = "458" + OPTION459 = "459" + OPTION460 = "460" + OPTION461 = "461" + OPTION462 = "462" + OPTION463 = "463" + OPTION464 = "464" + OPTION465 = "465" + OPTION466 = "466" + OPTION467 = "467" + OPTION468 = "468" + OPTION469 = "469" + OPTION470 = "470" + OPTION471 = "471" + OPTION472 = "472" + OPTION473 = "473" + OPTION474 = "474" + OPTION475 = "475" + OPTION476 = "476" + OPTION477 = "477" + OPTION478 = "478" + OPTION479 = "479" + OPTION480 = "480" + OPTION481 = "481" + OPTION482 = "482" + OPTION483 = "483" + OPTION484 = "484" + OPTION485 = "485" + OPTION486 = "486" + OPTION487 = "487" + OPTION488 = "488" + OPTION489 = "489" + OPTION490 = "490" + OPTION491 = "491" + OPTION492 = "492" + OPTION493 = "493" + OPTION494 = "494" + OPTION495 = "495" + OPTION496 = "496" + OPTION497 = "497" + OPTION498 = "498" + OPTION499 = "499" + +def f(x: Intersection[Huge, Not[Literal[Huge.OPTION499]]]): + # revealed: Literal[Huge.OPTION0, Huge.OPTION1, Huge.OPTION2, Huge.OPTION3, Huge.OPTION4, Huge.OPTION5, Huge.OPTION6, Huge.OPTION7, Huge.OPTION8, Huge.OPTION9, Huge.OPTION10, Huge.OPTION11, Huge.OPTION12, Huge.OPTION13, Huge.OPTION14, Huge.OPTION15, Huge.OPTION16, Huge.OPTION17, Huge.OPTION18, Huge.OPTION19, Huge.OPTION20, Huge.OPTION21, Huge.OPTION22, Huge.OPTION23, Huge.OPTION24, Huge.OPTION25, Huge.OPTION26, Huge.OPTION27, Huge.OPTION28, Huge.OPTION29, Huge.OPTION30, Huge.OPTION31, Huge.OPTION32, Huge.OPTION33, Huge.OPTION34, Huge.OPTION35, Huge.OPTION36, Huge.OPTION37, Huge.OPTION38, Huge.OPTION39, Huge.OPTION40, Huge.OPTION41, Huge.OPTION42, Huge.OPTION43, Huge.OPTION44, Huge.OPTION45, Huge.OPTION46, Huge.OPTION47, Huge.OPTION48, Huge.OPTION49, Huge.OPTION50, Huge.OPTION51, Huge.OPTION52, Huge.OPTION53, Huge.OPTION54, Huge.OPTION55, Huge.OPTION56, Huge.OPTION57, Huge.OPTION58, Huge.OPTION59, Huge.OPTION60, Huge.OPTION61, Huge.OPTION62, Huge.OPTION63, Huge.OPTION64, Huge.OPTION65, Huge.OPTION66, Huge.OPTION67, Huge.OPTION68, Huge.OPTION69, Huge.OPTION70, Huge.OPTION71, Huge.OPTION72, Huge.OPTION73, Huge.OPTION74, Huge.OPTION75, Huge.OPTION76, Huge.OPTION77, Huge.OPTION78, Huge.OPTION79, Huge.OPTION80, Huge.OPTION81, Huge.OPTION82, Huge.OPTION83, Huge.OPTION84, Huge.OPTION85, Huge.OPTION86, Huge.OPTION87, Huge.OPTION88, Huge.OPTION89, Huge.OPTION90, Huge.OPTION91, Huge.OPTION92, Huge.OPTION93, Huge.OPTION94, Huge.OPTION95, Huge.OPTION96, Huge.OPTION97, Huge.OPTION98, Huge.OPTION99, Huge.OPTION100, Huge.OPTION101, Huge.OPTION102, Huge.OPTION103, Huge.OPTION104, Huge.OPTION105, Huge.OPTION106, Huge.OPTION107, Huge.OPTION108, Huge.OPTION109, Huge.OPTION110, Huge.OPTION111, Huge.OPTION112, Huge.OPTION113, Huge.OPTION114, Huge.OPTION115, Huge.OPTION116, Huge.OPTION117, Huge.OPTION118, Huge.OPTION119, Huge.OPTION120, Huge.OPTION121, Huge.OPTION122, Huge.OPTION123, Huge.OPTION124, Huge.OPTION125, Huge.OPTION126, Huge.OPTION127, Huge.OPTION128, Huge.OPTION129, Huge.OPTION130, Huge.OPTION131, Huge.OPTION132, Huge.OPTION133, Huge.OPTION134, Huge.OPTION135, Huge.OPTION136, Huge.OPTION137, Huge.OPTION138, Huge.OPTION139, Huge.OPTION140, Huge.OPTION141, Huge.OPTION142, Huge.OPTION143, Huge.OPTION144, Huge.OPTION145, Huge.OPTION146, Huge.OPTION147, Huge.OPTION148, Huge.OPTION149, Huge.OPTION150, Huge.OPTION151, Huge.OPTION152, Huge.OPTION153, Huge.OPTION154, Huge.OPTION155, Huge.OPTION156, Huge.OPTION157, Huge.OPTION158, Huge.OPTION159, Huge.OPTION160, Huge.OPTION161, Huge.OPTION162, Huge.OPTION163, Huge.OPTION164, Huge.OPTION165, Huge.OPTION166, Huge.OPTION167, Huge.OPTION168, Huge.OPTION169, Huge.OPTION170, Huge.OPTION171, Huge.OPTION172, Huge.OPTION173, Huge.OPTION174, Huge.OPTION175, Huge.OPTION176, Huge.OPTION177, Huge.OPTION178, Huge.OPTION179, Huge.OPTION180, Huge.OPTION181, Huge.OPTION182, Huge.OPTION183, Huge.OPTION184, Huge.OPTION185, Huge.OPTION186, Huge.OPTION187, Huge.OPTION188, Huge.OPTION189, Huge.OPTION190, Huge.OPTION191, Huge.OPTION192, Huge.OPTION193, Huge.OPTION194, Huge.OPTION195, Huge.OPTION196, Huge.OPTION197, Huge.OPTION198, Huge.OPTION199, Huge.OPTION200, Huge.OPTION201, Huge.OPTION202, Huge.OPTION203, Huge.OPTION204, Huge.OPTION205, Huge.OPTION206, Huge.OPTION207, Huge.OPTION208, Huge.OPTION209, Huge.OPTION210, Huge.OPTION211, Huge.OPTION212, Huge.OPTION213, Huge.OPTION214, Huge.OPTION215, Huge.OPTION216, Huge.OPTION217, Huge.OPTION218, Huge.OPTION219, Huge.OPTION220, Huge.OPTION221, Huge.OPTION222, Huge.OPTION223, Huge.OPTION224, Huge.OPTION225, Huge.OPTION226, Huge.OPTION227, Huge.OPTION228, Huge.OPTION229, Huge.OPTION230, Huge.OPTION231, Huge.OPTION232, Huge.OPTION233, Huge.OPTION234, Huge.OPTION235, Huge.OPTION236, Huge.OPTION237, Huge.OPTION238, Huge.OPTION239, Huge.OPTION240, Huge.OPTION241, Huge.OPTION242, Huge.OPTION243, Huge.OPTION244, Huge.OPTION245, Huge.OPTION246, Huge.OPTION247, Huge.OPTION248, Huge.OPTION249, Huge.OPTION250, Huge.OPTION251, Huge.OPTION252, Huge.OPTION253, Huge.OPTION254, Huge.OPTION255, Huge.OPTION256, Huge.OPTION257, Huge.OPTION258, Huge.OPTION259, Huge.OPTION260, Huge.OPTION261, Huge.OPTION262, Huge.OPTION263, Huge.OPTION264, Huge.OPTION265, Huge.OPTION266, Huge.OPTION267, Huge.OPTION268, Huge.OPTION269, Huge.OPTION270, Huge.OPTION271, Huge.OPTION272, Huge.OPTION273, Huge.OPTION274, Huge.OPTION275, Huge.OPTION276, Huge.OPTION277, Huge.OPTION278, Huge.OPTION279, Huge.OPTION280, Huge.OPTION281, Huge.OPTION282, Huge.OPTION283, Huge.OPTION284, Huge.OPTION285, Huge.OPTION286, Huge.OPTION287, Huge.OPTION288, Huge.OPTION289, Huge.OPTION290, Huge.OPTION291, Huge.OPTION292, Huge.OPTION293, Huge.OPTION294, Huge.OPTION295, Huge.OPTION296, Huge.OPTION297, Huge.OPTION298, Huge.OPTION299, Huge.OPTION300, Huge.OPTION301, Huge.OPTION302, Huge.OPTION303, Huge.OPTION304, Huge.OPTION305, Huge.OPTION306, Huge.OPTION307, Huge.OPTION308, Huge.OPTION309, Huge.OPTION310, Huge.OPTION311, Huge.OPTION312, Huge.OPTION313, Huge.OPTION314, Huge.OPTION315, Huge.OPTION316, Huge.OPTION317, Huge.OPTION318, Huge.OPTION319, Huge.OPTION320, Huge.OPTION321, Huge.OPTION322, Huge.OPTION323, Huge.OPTION324, Huge.OPTION325, Huge.OPTION326, Huge.OPTION327, Huge.OPTION328, Huge.OPTION329, Huge.OPTION330, Huge.OPTION331, Huge.OPTION332, Huge.OPTION333, Huge.OPTION334, Huge.OPTION335, Huge.OPTION336, Huge.OPTION337, Huge.OPTION338, Huge.OPTION339, Huge.OPTION340, Huge.OPTION341, Huge.OPTION342, Huge.OPTION343, Huge.OPTION344, Huge.OPTION345, Huge.OPTION346, Huge.OPTION347, Huge.OPTION348, Huge.OPTION349, Huge.OPTION350, Huge.OPTION351, Huge.OPTION352, Huge.OPTION353, Huge.OPTION354, Huge.OPTION355, Huge.OPTION356, Huge.OPTION357, Huge.OPTION358, Huge.OPTION359, Huge.OPTION360, Huge.OPTION361, Huge.OPTION362, Huge.OPTION363, Huge.OPTION364, Huge.OPTION365, Huge.OPTION366, Huge.OPTION367, Huge.OPTION368, Huge.OPTION369, Huge.OPTION370, Huge.OPTION371, Huge.OPTION372, Huge.OPTION373, Huge.OPTION374, Huge.OPTION375, Huge.OPTION376, Huge.OPTION377, Huge.OPTION378, Huge.OPTION379, Huge.OPTION380, Huge.OPTION381, Huge.OPTION382, Huge.OPTION383, Huge.OPTION384, Huge.OPTION385, Huge.OPTION386, Huge.OPTION387, Huge.OPTION388, Huge.OPTION389, Huge.OPTION390, Huge.OPTION391, Huge.OPTION392, Huge.OPTION393, Huge.OPTION394, Huge.OPTION395, Huge.OPTION396, Huge.OPTION397, Huge.OPTION398, Huge.OPTION399, Huge.OPTION400, Huge.OPTION401, Huge.OPTION402, Huge.OPTION403, Huge.OPTION404, Huge.OPTION405, Huge.OPTION406, Huge.OPTION407, Huge.OPTION408, Huge.OPTION409, Huge.OPTION410, Huge.OPTION411, Huge.OPTION412, Huge.OPTION413, Huge.OPTION414, Huge.OPTION415, Huge.OPTION416, Huge.OPTION417, Huge.OPTION418, Huge.OPTION419, Huge.OPTION420, Huge.OPTION421, Huge.OPTION422, Huge.OPTION423, Huge.OPTION424, Huge.OPTION425, Huge.OPTION426, Huge.OPTION427, Huge.OPTION428, Huge.OPTION429, Huge.OPTION430, Huge.OPTION431, Huge.OPTION432, Huge.OPTION433, Huge.OPTION434, Huge.OPTION435, Huge.OPTION436, Huge.OPTION437, Huge.OPTION438, Huge.OPTION439, Huge.OPTION440, Huge.OPTION441, Huge.OPTION442, Huge.OPTION443, Huge.OPTION444, Huge.OPTION445, Huge.OPTION446, Huge.OPTION447, Huge.OPTION448, Huge.OPTION449, Huge.OPTION450, Huge.OPTION451, Huge.OPTION452, Huge.OPTION453, Huge.OPTION454, Huge.OPTION455, Huge.OPTION456, Huge.OPTION457, Huge.OPTION458, Huge.OPTION459, Huge.OPTION460, Huge.OPTION461, Huge.OPTION462, Huge.OPTION463, Huge.OPTION464, Huge.OPTION465, Huge.OPTION466, Huge.OPTION467, Huge.OPTION468, Huge.OPTION469, Huge.OPTION470, Huge.OPTION471, Huge.OPTION472, Huge.OPTION473, Huge.OPTION474, Huge.OPTION475, Huge.OPTION476, Huge.OPTION477, Huge.OPTION478, Huge.OPTION479, Huge.OPTION480, Huge.OPTION481, Huge.OPTION482, Huge.OPTION483, Huge.OPTION484, Huge.OPTION485, Huge.OPTION486, Huge.OPTION487, Huge.OPTION488, Huge.OPTION489, Huge.OPTION490, Huge.OPTION491, Huge.OPTION492, Huge.OPTION493, Huge.OPTION494, Huge.OPTION495, Huge.OPTION496, Huge.OPTION497, Huge.OPTION498] + reveal_type(x) +``` + ## Simplifying gradually-equivalent types If two types are gradually equivalent, we can keep just one of them in a union: diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 5265e820a0..34bdc816b1 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -39,28 +39,42 @@ use crate::types::enums::{enum_member_literals, enum_metadata}; use crate::types::type_ordering::union_or_intersection_elements_ordering; use crate::types::{ - BytesLiteralType, IntersectionType, KnownClass, NegativeIntersectionElements, - StringLiteralType, Type, TypeVarBoundOrConstraints, UnionType, + BytesLiteralType, ClassLiteral, EnumLiteralType, IntersectionType, KnownClass, + NegativeIntersectionElements, StringLiteralType, Type, TypeVarBoundOrConstraints, UnionType, }; use crate::{Db, FxOrderSet}; -use rustc_hash::FxHashSet; use smallvec::SmallVec; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum LiteralKind { +enum LiteralKind<'db> { Int, String, Bytes, + Enum { enum_class: ClassLiteral<'db> }, } impl<'db> Type<'db> { /// Return `true` if this type can be a supertype of some literals of `kind` and not others. fn splits_literals(self, db: &'db dyn Db, kind: LiteralKind) -> bool { match (self, kind) { + // Note that as of 2026-01-04, `AlwaysFalsy` and `AlwaysTruthy` never split + // enum literals, but that could change in the future. `Literal[Foo.X]` could + // plausibly be understood by ty as a subtype of `AlwaysFalsy` in the following + // snippet, because `Foo` is an IntEnum that does not override `__bool__` and + // `Foo.X` has a falsy value whereas `Foo.Y` does not: + // + // ```py + // class Foo(enum.IntEnum): + // X = 0 + // Y = 1 + // ``` (Type::AlwaysFalsy | Type::AlwaysTruthy, _) => true, (Type::StringLiteral(_), LiteralKind::String) => true, (Type::BytesLiteral(_), LiteralKind::Bytes) => true, (Type::IntLiteral(_), LiteralKind::Int) => true, + (Type::EnumLiteral(enum_literal), LiteralKind::Enum { enum_class }) => { + enum_literal.enum_class(db) == enum_class + } (Type::Intersection(intersection), _) => { intersection .positive(db) @@ -85,17 +99,14 @@ enum UnionElement<'db> { IntLiterals(FxOrderSet), StringLiterals(FxOrderSet>), BytesLiterals(FxOrderSet>), + EnumLiterals { + enum_class: ClassLiteral<'db>, + literals: FxOrderSet>, + }, Type(Type<'db>), } impl<'db> UnionElement<'db> { - const fn to_type_element(&self) -> Option> { - match self { - UnionElement::Type(ty) => Some(*ty), - _ => None, - } - } - /// Try reducing this `UnionElement` given the presence in the same union of `other_type`. fn try_reduce(&mut self, db: &'db dyn Db, other_type: Type<'db>) -> ReduceResult<'db> { let mut other_type_negated_cache = None; @@ -160,6 +171,20 @@ impl<'db> UnionElement<'db> { !Type::BytesLiteral(literals[0]).is_redundant_with(db, other_type) } } + UnionElement::EnumLiterals { + enum_class, + literals, + } => { + let literal_kind = LiteralKind::Enum { + enum_class: *enum_class, + }; + if other_type.splits_literals(db, literal_kind) { + literals.retain(|literal| should_retain_type(Type::EnumLiteral(*literal))); + !literals.is_empty() + } else { + !Type::EnumLiteral(literals[0]).is_redundant_with(db, other_type) + } + } UnionElement::Type(existing) => return ReduceResult::Type(*existing), }; @@ -205,19 +230,26 @@ impl RecursivelyDefined { } } -/// If the value ​​is defined recursively, widening is performed from fewer literal elements, resulting in faster convergence of the fixed-point iteration. +/// If the value ​​is defined recursively, widening is performed from fewer literal elements, +/// resulting in faster convergence of the fixed-point iteration. const MAX_RECURSIVE_UNION_LITERALS: usize = 10; /// If the value ​​is defined non-recursively, the fixed-point iteration will converge in one go, -/// so in principle we can have as many literal elements as we want, but to avoid unintended huge computational loads, we limit it to 256. +/// so in principle we can have as many literal elements as we want, +/// but to avoid unintended huge computational loads, we limit it to 256. const MAX_NON_RECURSIVE_UNION_LITERALS: usize = 256; +/// However, we set a much larger limit for enum literals than for other kinds of literals. +/// Huge enums are not uncommon (especially in generated code), and it's annoying +/// if reachability analysis etc. fails when analysing these enums. +const MAX_NON_RECURSIVE_UNION_ENUM_LITERALS: usize = 8192; pub(crate) struct UnionBuilder<'db> { elements: Vec>, db: &'db dyn Db, unpack_aliases: bool, order_elements: bool, - // This is enabled when joining types in a `cycle_recovery` function. - // Since a cycle cannot be created within a `cycle_recovery` function, execution of `is_redundant_with` is skipped. + /// This is enabled when joining types in a `cycle_recovery` function. + /// Since a cycle cannot be created within a `cycle_recovery` function, + /// execution of `is_redundant_with` is skipped. cycle_recovery: bool, recursively_defined: RecursivelyDefined, } @@ -280,6 +312,9 @@ impl<'db> UnionBuilder<'db> { UnionElement::BytesLiterals(_) => { replace_with.push(KnownClass::Bytes.to_instance(self.db)); } + UnionElement::EnumLiterals { literals, .. } => { + replace_with.push(literals[0].enum_class_instance(self.db)); + } UnionElement::Type(_) => {} } } @@ -327,6 +362,7 @@ impl<'db> UnionBuilder<'db> { UnionElement::IntLiterals(literals) => acc + literals.len(), UnionElement::StringLiterals(literals) => acc + literals.len(), UnionElement::BytesLiterals(literals) => acc + literals.len(), + UnionElement::EnumLiterals { literals, .. } => acc + literals.len(), UnionElement::Type(_) => acc, }); if should_widen(literals, self.recursively_defined) { @@ -492,32 +528,78 @@ impl<'db> UnionBuilder<'db> { let metadata = enum_metadata(self.db, enum_class).expect("Class of enum literal is an enum"); - let enum_members_in_union = self - .elements - .iter() - .filter_map(UnionElement::to_type_element) - .filter_map(Type::as_enum_literal) - .map(|literal| literal.name(self.db)) - .chain(std::iter::once(enum_member_to_add.name(self.db))) - .collect::>(); - - let all_members_are_in_union = metadata - .members - .keys() - .all(|name| enum_members_in_union.contains(name)); - - if all_members_are_in_union { + if metadata.members.len() == 1 { self.add_in_place_impl( enum_member_to_add.enum_class_instance(self.db), seen_aliases, ); - } else if !self - .elements - .iter() - .filter_map(UnionElement::to_type_element) - .any(|ty| Type::EnumLiteral(enum_member_to_add).is_subtype_of(self.db, ty)) - { - self.push_type(Type::EnumLiteral(enum_member_to_add), seen_aliases); + return; + } + + let mut found = None; + let mut to_remove = None; + for (index, element) in self.elements.iter_mut().enumerate() { + match element { + UnionElement::EnumLiterals { + enum_class: existing_enum_class, + literals, + } => { + if *existing_enum_class != enum_class { + continue; + } + // See the doc-comment above `MAX_NON_RECURSIVE_UNION_ENUM_LITERALS` + // for why we avoid using the `should_widen` closure here. + let enum_literals_limit = + if self.recursively_defined.is_yes() && cycle_recovery { + MAX_RECURSIVE_UNION_LITERALS + } else { + MAX_NON_RECURSIVE_UNION_ENUM_LITERALS + }; + if literals.len() >= enum_literals_limit { + let replace_with = literals[0].enum_class_instance(self.db); + self.add_in_place_impl(replace_with, seen_aliases); + return; + } + found = Some(literals); + continue; + } + UnionElement::Type(existing) => { + if ty.is_redundant_with(self.db, *existing) { + return; + } + // e.g. `existing` could be `Literal[Foo.X] & Any`, + // and `ty` could be `Literal[Foo.X]` + if existing.is_redundant_with(self.db, ty) { + to_remove = Some(index); + continue; + } + if ty_negated().is_subtype_of(self.db, *existing) { + // The type that includes both this new element, and its negation + // (or a supertype of its negation), must be simply `object`. + self.collapse_to_object(); + return; + } + } + _ => {} + } + } + if let Some(found) = found { + let newly_added = found.insert(enum_member_to_add); + if newly_added && found.len() == metadata.members.len() { + self.add_in_place_impl( + enum_member_to_add.enum_class_instance(self.db), + seen_aliases, + ); + return; + } + } else { + self.elements.push(UnionElement::EnumLiterals { + enum_class, + literals: FxOrderSet::from_iter([enum_member_to_add]), + }); + } + if let Some(index) = to_remove { + self.elements.swap_remove(index); } } // Adding `object` to a union results in `object`. @@ -636,6 +718,9 @@ impl<'db> UnionBuilder<'db> { UnionElement::BytesLiterals(literals) => { types.extend(literals.into_iter().map(Type::BytesLiteral)); } + UnionElement::EnumLiterals { literals, .. } => { + types.extend(literals.into_iter().map(Type::EnumLiteral)); + } UnionElement::Type(ty) => types.push(ty), } }