1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397
|
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
(* Detection of partial matches and unused match cases. *)
open Misc
open Asttypes
open Types
open Data_types
open Typedtree
type 'pattern parmatch_case =
{ pattern : 'pattern;
has_guard : bool;
needs_refute : bool;
}
let typed_case { c_lhs; c_guard; c_rhs } =
{ pattern = c_lhs;
has_guard = Option.is_some c_guard;
needs_refute = (c_rhs.exp_desc = Texp_unreachable);
}
let untyped_case { Parsetree.pc_lhs; pc_guard; pc_rhs } =
{ pattern = pc_lhs;
has_guard = Option.is_some pc_guard;
needs_refute = (pc_rhs.pexp_desc = Parsetree.Pexp_unreachable);
}
(*************************************)
(* Utilities for building patterns *)
(*************************************)
let make_pat desc ty tenv =
{pat_desc = desc; pat_loc = Location.none; pat_extra = [];
pat_type = ty ; pat_env = tenv;
pat_attributes = [];
}
let omega = Patterns.omega
let omegas = Patterns.omegas
let omega_list = Patterns.omega_list
let extra_pat =
make_pat
(Tpat_var (Ident.create_local "+", mknoloc "+",
Uid.internal_not_actually_unique))
Ctype.none Env.empty
(*******************)
(* Coherence check *)
(*******************)
(* For some of the operations we do in this module, we would like (because it
simplifies matters) to assume that patterns appearing on a given column in a
pattern matrix are /coherent/ (think "of the same type").
Unfortunately that is not always true.
Consider the following (well-typed) example:
{[
type _ t = S : string t | U : unit t
let f (type a) (t1 : a t) (t2 : a t) (a : a) =
match t1, t2, a with
| U, _, () -> ()
| _, S, "" -> ()
]}
Clearly the 3rd column contains incoherent patterns.
On the example above, most of the algorithms will explore the pattern matrix
as illustrated by the following tree:
{v
S
-------> | "" |
U | S, "" | __/ | () |
--------> | _, () | \ not S
| U, _, () | __/ -------> | () |
| _, S, "" | \
---------> | S, "" | ----------> | "" |
not U S
v}
where following an edge labelled by a pattern P means "assuming the value I
am matching on is filtered by [P] on the column I am currently looking at,
then the following submatrix is still reachable".
Notice that at any point of that tree, if the first column of a matrix is
incoherent, then the branch leading to it can only be taken if the scrutinee
is ill-typed.
In the example above the only case where we have a matrix with an incoherent
first column is when we consider [t1, t2, a] to be [U, S, ...]. However such
a value would be ill-typed, so we can never actually get there.
Checking the first column at each step of the recursion and making the
conscious decision of "aborting" the algorithm whenever the first column
becomes incoherent, allows us to retain the initial assumption in later
stages of the algorithms.
---
N.B. two patterns can be considered coherent even though they might not be of
the same type.
That's in part because we only care about the "head" of patterns and leave
checking coherence of subpatterns for the next steps of the algorithm:
('a', 'b') and (1, ()) will be deemed coherent because they are both a tuples
of arity 2 (we'll notice at a later stage the incoherence of 'a' and 1).
But also because it can be hard/costly to determine exactly whether two
patterns are of the same type or not (eg. in the example above with _ and S,
but see also the module [Coherence_illustration] in
testsuite/tests/basic-more/robustmatch.ml).
For the moment our weak, loosely-syntactic, coherence check seems to be
enough and we leave it to each user to consider (and document!) what happens
when an "incoherence" is not detected by this check.
*)
(* Given the first column of a simplified matrix, this function first looks for
a "discriminating" pattern on that column (i.e. a non-omega one) and then
check that every other head pattern in the column is coherent with that one.
*)
let all_coherent column =
let open Patterns.Head in
let coherent_heads hp1 hp2 =
match hp1.pat_desc, hp2.pat_desc with
| Construct c, Construct c' ->
c.cstr_consts = c'.cstr_consts
&& c.cstr_nonconsts = c'.cstr_nonconsts
| Constant c1, Constant c2 -> begin
match c1, c2 with
| Const_char _, Const_char _
| Const_int _, Const_int _
| Const_int32 _, Const_int32 _
| Const_int64 _, Const_int64 _
| Const_nativeint _, Const_nativeint _
| Const_float _, Const_float _
| Const_string _, Const_string _ -> true
| ( Const_char _
| Const_int _
| Const_int32 _
| Const_int64 _
| Const_nativeint _
| Const_float _
| Const_string _), _ -> false
end
| Tuple l1, Tuple l2 -> l1 = l2
| Record (lbl1 :: _), Record (lbl2 :: _) ->
Array.length lbl1.lbl_all = Array.length lbl2.lbl_all
| Array (am1, _), Array (am2, _) -> am1 = am2
| Any, _
| _, Any
| Record [], Record []
| Variant _, Variant _
| Lazy, Lazy -> true
| _, _ -> false
in
match
List.find
(function
| { pat_desc = Any } -> false
| _ -> true)
column
with
| exception Not_found ->
(* only omegas on the column: the column is coherent. *)
true
| discr_pat ->
List.for_all (coherent_heads discr_pat) column
let first_column simplified_matrix =
List.map (fun ((head, _args), _rest) -> head) simplified_matrix
(***********************)
(* Compatibility check *)
(***********************)
(* Patterns p and q compatible means:
there exists value V that matches both, However....
The case of extension types is dubious, as constructor rebind permits
that different constructors are the same (and are thus compatible).
Compilation must take this into account, consider:
type t = ..
type t += A|B
type t += C=A
let f x y = match x,y with
| true,A -> '1'
| _,C -> '2'
| false,A -> '3'
| _,_ -> '_'
As C is bound to A the value of f false A is '2' (and not '3' as it would
be in the absence of rebinding).
Not considering rebinding, patterns "false,A" and "_,C" are incompatible
and the compiler can swap the second and third clause, resulting in the
(more efficiently compiled) matching
match x,y with
| true,A -> '1'
| false,A -> '3'
| _,C -> '2'
| _,_ -> '_'
This is not correct: when C is bound to A, "f false A" returns '2' (not '3')
However, diagnostics do not take constructor rebinding into account.
Notice, that due to module abstraction constructor rebinding is hidden.
module X : sig type t = .. type t += A|B end = struct
type t = ..
type t += A
type t += B=A
end
open X
let f x = match x with
| A -> '1'
| B -> '2'
| _ -> '_'
The second clause above will NOT (and cannot) be flagged as useless.
Finally, there are two compatibility functions:
compat p q ---> 'syntactic compatibility, used for diagnostics.
may_compat p q ---> a safe approximation of possible compat,
for compilation
*)
let is_absent tag row = row_field_repr (get_row_field tag !row) = Rabsent
let is_absent_pat d =
match d.pat_desc with
| Patterns.Head.Variant { tag; cstr_row; _ } -> is_absent tag cstr_row
| _ -> false
let const_compare x y =
match x,y with
| Const_float f1, Const_float f2 ->
Stdlib.compare (float_of_string f1) (float_of_string f2)
| Const_string (s1, _, _), Const_string (s2, _, _) ->
String.compare s1 s2
| (Const_int _
|Const_char _
|Const_string (_, _, _)
|Const_float _
|Const_int32 _
|Const_int64 _
|Const_nativeint _
), _ -> Stdlib.compare x y
let records_args l1 l2 =
(* Invariant: fields are already sorted by Typecore.type_label_a_list *)
let rec combine r1 r2 l1 l2 = match l1,l2 with
| [],[] -> List.rev r1, List.rev r2
| [],(_,_,p2)::rem2 -> combine (omega::r1) (p2::r2) [] rem2
| (_,_,p1)::rem1,[] -> combine (p1::r1) (omega::r2) rem1 []
| (_,lbl1,p1)::rem1, ( _,lbl2,p2)::rem2 ->
if lbl1.lbl_pos < lbl2.lbl_pos then
combine (p1::r1) (omega::r2) rem1 l2
else if lbl1.lbl_pos > lbl2.lbl_pos then
combine (omega::r1) (p2::r2) l1 rem2
else (* same label on both sides *)
combine (p1::r1) (p2::r2) rem1 rem2 in
combine [] [] l1 l2
module Compat
(Constr:sig
val equal :
Data_types.constructor_description ->
Data_types.constructor_description ->
bool
end) = struct
let rec compat p q = match p.pat_desc,q.pat_desc with
(* Variables match any value *)
| ((Tpat_any|Tpat_var _),_)
| (_,(Tpat_any|Tpat_var _)) -> true
(* Structural induction *)
| Tpat_alias (p,_,_,_,_),_ -> compat p q
| _,Tpat_alias (q,_,_,_,_) -> compat p q
| Tpat_or (p1,p2,_),_ ->
(compat p1 q || compat p2 q)
| _,Tpat_or (q1,q2,_) ->
(compat p q1 || compat p q2)
(* Constructors, with special case for extension *)
| Tpat_construct (_, c1, ps1, _), Tpat_construct (_, c2, ps2, _) ->
Constr.equal c1 c2 && compats ps1 ps2
(* More standard stuff *)
| Tpat_variant(l1,op1, _), Tpat_variant(l2,op2,_) ->
l1=l2 && ocompat op1 op2
| Tpat_constant c1, Tpat_constant c2 ->
const_compare c1 c2 = 0
| Tpat_tuple labeled_ps, Tpat_tuple labeled_qs ->
tuple_compat labeled_ps labeled_qs
| Tpat_lazy p, Tpat_lazy q -> compat p q
| Tpat_record (l1,_),Tpat_record (l2,_) ->
let ps,qs = records_args l1 l2 in
compats ps qs
| Tpat_array (am1, ps), Tpat_array (am2, qs) ->
am1 = am2 &&
List.length ps = List.length qs &&
compats ps qs
| _,_ -> false
and ocompat op oq = match op,oq with
| None,None -> true
| Some p,Some q -> compat p q
| (None,Some _)|(Some _,None) -> false
and compats ps qs = match ps,qs with
| [], [] -> true
| p::ps, q::qs -> compat p q && compats ps qs
| _,_ -> false
and tuple_compat labeled_ps labeled_qs = match labeled_ps,labeled_qs with
| [], [] -> true
| (p_label, p)::labeled_ps, (q_label, q)::labeled_qs ->
Option.equal String.equal p_label q_label
&& compat p q && tuple_compat labeled_ps labeled_qs
| _,_ -> false
end
module SyntacticCompat =
Compat
(struct
let equal = Data_types.equal_constr
end)
let compat = SyntacticCompat.compat
and compats = SyntacticCompat.compats
(* Due to (potential) rebinding, two extension constructors
of the same arity type may equal *)
exception Empty (* Empty pattern *)
(****************************************)
(* Utilities for retrieving type paths *)
(****************************************)
(* May need a clean copy, cf. PR#4745 *)
let clean_copy ty =
if get_level ty = Btype.generic_level then ty
else Subst.type_expr Subst.identity ty
let get_constructor_type_path ty tenv =
let ty = Ctype.expand_head tenv (clean_copy ty) in
match get_desc ty with
| Tconstr (path,_,_) -> path
| _ -> assert false
(****************************)
(* Utilities for matching *)
(****************************)
(* Check top matching *)
let simple_match d h =
let open Patterns.Head in
match d.pat_desc, h.pat_desc with
| Construct c1, Construct c2 ->
Data_types.equal_constr c1 c2
| Variant { tag = t1; _ }, Variant { tag = t2 } ->
t1 = t2
| Constant c1, Constant c2 -> const_compare c1 c2 = 0
| Lazy, Lazy -> true
| Record _, Record _ -> true
| Tuple lbls1, Tuple lbls2 -> lbls1 = lbls2
| Array (am1, len1), Array (am2, len2) -> am1 = am2 && len1 = len2
| _, Any -> true
| _, _ -> false
(* extract record fields as a whole *)
let record_arg ph =
let open Patterns.Head in
match ph.pat_desc with
| Any -> []
| Record args -> args
| _ -> fatal_error "Parmatch.as_record"
let extract_fields lbls arg =
let get_field pos arg =
match List.find (fun (lbl,_) -> pos = lbl.lbl_pos) arg with
| _, p -> p
| exception Not_found -> omega
in
List.map (fun lbl -> get_field lbl.lbl_pos arg) lbls
(* Build argument list when p2 >= p1, where p1 is a simple pattern *)
let simple_match_args discr head args =
let open Patterns.Head in
match head.pat_desc with
| Constant _ -> []
| Construct _
| Variant _
| Tuple _
| Array _
| Lazy -> args
| Record lbls -> extract_fields (record_arg discr) (List.combine lbls args)
| Any ->
begin match discr.pat_desc with
| Construct cstr -> Patterns.omegas cstr.cstr_arity
| Variant { has_arg = true }
| Lazy -> [Patterns.omega]
| Record lbls -> omega_list lbls
| Array (_, len) -> Patterns.omegas len
| Tuple lbls -> omega_list lbls
| Variant { has_arg = false }
| Any
| Constant _ -> []
end
(* Consider a pattern matrix whose first column has been simplified to contain
only _ or a head constructor
| p1, r1...
| p2, r2...
| p3, r3...
| ...
We build a normalized /discriminating/ pattern from a pattern [q] by folding
over the first column of the matrix, "refining" [q] as we go:
- when we encounter a row starting with [Tuple] or [Lazy] then we
can stop and return that head, as we cannot refine any further. Indeed,
these constructors are alone in their signature, so they will subsume
whatever other head we might find, as well as the head we're threading
along.
- when we find a [Record] then it is a bit more involved: it is also alone
in its signature, however it might only be matching a subset of the
record fields. We use these fields to refine our accumulator and keep going
as another row might match on different fields.
- rows starting with a wildcard do not bring any information, so we ignore
them and keep going
- if we encounter anything else (i.e. any other constructor), then we just
stop and return our accumulator.
*)
let discr_pat q pss =
let open Patterns.Head in
let rec refine_pat acc = function
| [] -> acc
| ((head, _), _) :: rows ->
match head.pat_desc with
| Any -> refine_pat acc rows
| Tuple _ | Lazy -> head
| Record lbls ->
(* N.B. we could make this case "simpler" by refining the record case
using [all_record_args].
In which case we wouldn't need to fold over the first column for
records.
However it makes the witness we generate for the exhaustivity warning
less pretty. *)
let fields =
List.fold_right (fun lbl r ->
if List.exists (fun l -> l.lbl_pos = lbl.lbl_pos) r then
r
else
lbl :: r
) lbls (record_arg acc)
in
let d = { head with pat_desc = Record fields } in
refine_pat d rows
| _ -> acc
in
let q, _ = deconstruct q in
match q.pat_desc with
(* short-circuiting: clearly if we have anything other than [Record] or
[Any] to start with, we're not going to be able refine at all. So
there's no point going over the matrix. *)
| Any | Record _ -> refine_pat q pss
| _ -> q
(*
In case a matching value is found, set actual arguments
of the matching pattern.
*)
let rec read_args xs r = match xs,r with
| [],_ -> [],r
| _::xs, arg::rest ->
let args,rest = read_args xs rest in
arg::args,rest
| _,_ ->
fatal_error "Parmatch.read_args"
let set_args q r = match q with
| {pat_desc = Tpat_tuple lbls_omegas} ->
let lbls, omegas = List.split lbls_omegas in
let args, rest = read_args omegas r in
make_pat (Tpat_tuple (List.combine lbls args)) q.pat_type q.pat_env :: rest
| {pat_desc = Tpat_record (omegas,closed)} ->
let args,rest = read_args omegas r in
let args =
List.map2 (fun (lid, lbl, _) arg -> (lid, lbl, arg)) omegas args in
make_pat (Tpat_record (args, closed)) q.pat_type q.pat_env :: rest
| {pat_desc = Tpat_construct (lid, c, omegas, _)} ->
let args,rest = read_args omegas r in
make_pat
(Tpat_construct (lid, c, args, None))
q.pat_type q.pat_env::
rest
| {pat_desc = Tpat_variant (l, omega, row)} ->
let arg, rest =
match omega, r with
Some _, a::r -> Some a, r
| None, r -> None, r
| _ -> assert false
in
make_pat
(Tpat_variant (l, arg, row)) q.pat_type q.pat_env::
rest
| {pat_desc = Tpat_lazy _omega} ->
begin match r with
arg::rest ->
make_pat (Tpat_lazy arg) q.pat_type q.pat_env::rest
| _ -> fatal_error "Parmatch.do_set_args (lazy)"
end
| {pat_desc = Tpat_array (am, omegas)} ->
let args,rest = read_args omegas r in
make_pat
(Tpat_array (am, args)) q.pat_type q.pat_env::
rest
| {pat_desc=Tpat_constant _|Tpat_any} ->
q::r (* case any is used in matching.ml *)
| {pat_desc = (Tpat_var _ | Tpat_alias _ | Tpat_or _); _} ->
fatal_error "Parmatch.set_args"
(* Given a matrix of non-empty rows
p1 :: r1...
p2 :: r2...
p3 :: r3...
Simplify the first column [p1 p2 p3] by splitting all or-patterns.
The result is a list of pairs
((pattern head, arguments), rest of row)
For example,
x :: r1
(Some _) as y :: r2
(None as x) as y :: r3
(Some x | (None as x)) :: r4
becomes
(( _ , [ ] ), r1)
(( Some, [_] ), r2)
(( None, [ ] ), r3)
(( Some, [x] ), r4)
(( None, [ ] ), r4)
*)
let simplify_head_pat ~add_column p ps k =
let rec simplify_head_pat p ps k =
match Patterns.General.(view p |> strip_vars).pat_desc with
| `Or (p1,p2,_) -> simplify_head_pat p1 ps (simplify_head_pat p2 ps k)
| #Patterns.Simple.view as view ->
add_column (Patterns.Head.deconstruct { p with pat_desc = view }) ps k
in simplify_head_pat p ps k
let rec simplify_first_col = function
| [] -> []
| [] :: _ -> assert false (* the rows are non-empty! *)
| (p::ps) :: rows ->
let add_column p ps k = (p, ps) :: k in
simplify_head_pat ~add_column p ps (simplify_first_col rows)
(* Builds the specialized matrix of [pss] according to the discriminating
pattern head [d].
See section 3.1 of http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
NOTES:
- we are polymorphic on the type of matrices we work on, in particular a row
might not simply be a [pattern list]. That's why we have the [extend_row]
parameter.
*)
let build_specialized_submatrix ~extend_row discr pss =
let rec filter_rec = function
| ((head, args), ps) :: pss ->
if simple_match discr head
then extend_row (simple_match_args discr head args) ps :: filter_rec pss
else filter_rec pss
| _ -> [] in
filter_rec pss
(* The "default" and "specialized" matrices of a given matrix.
See section 3.1 of http://moscova.inria.fr/~maranget/papers/warn/warn.pdf .
*)
type 'matrix specialized_matrices = {
default : 'matrix;
constrs : (Patterns.Head.t * 'matrix) list;
}
(* Consider a pattern matrix whose first column has been simplified
to contain only _ or a head constructor
| p1, r1...
| p2, r2...
| p3, r3...
| ...
We split this matrix into a list of /specialized/ sub-matrices, one for
each head constructor appearing in the first column. For each row whose
first column starts with a head constructor, remove this head
column, prepend one column for each argument of the constructor,
and add the resulting row in the sub-matrix corresponding to this
head constructor.
Rows whose left column is omega (the Any pattern _) may match any
head constructor, so they are added to all sub-matrices.
In the case where all the rows in the matrix have an omega on their first
column, then there is only one /specialized/ sub-matrix, formed of all these
omega rows.
This matrix is also called the /default/ matrix.
See the documentation of [build_specialized_submatrix] for an explanation of
the [extend_row] parameter.
*)
let build_specialized_submatrices ~extend_row discr rows =
let extend_group discr p args r rs =
let r = extend_row (simple_match_args discr p args) r in
(discr, r :: rs)
in
(* insert a row of head [p] and rest [r] into the right group
Note: with this implementation, the order of the groups
is the order of their first row in the source order.
This is a nice property to get exhaustivity counter-examples
in source order.
*)
let rec insert_constr head args r = function
| [] ->
(* if no group matched this row, it has a head constructor that
was never seen before; add a new sub-matrix for this head *)
[extend_group head head args r []]
| (q0,rs) as bd::env ->
if simple_match q0 head
then extend_group q0 head args r rs :: env
else bd :: insert_constr head args r env
in
(* insert a row of head omega into all groups *)
let insert_omega r env =
List.map (fun (q0,rs) -> extend_group q0 Patterns.Head.omega [] r rs) env
in
let rec form_groups constr_groups omega_tails = function
| [] -> (constr_groups, omega_tails)
| ((head, args), tail) :: rest ->
match head.pat_desc with
| Patterns.Head.Any ->
(* note that calling insert_omega here would be wrong
as some groups may not have been formed yet, if the
first row with this head pattern comes after in the list *)
form_groups constr_groups (tail :: omega_tails) rest
| _ ->
form_groups
(insert_constr head args tail constr_groups) omega_tails rest
in
let constr_groups, omega_tails =
let initial_constr_group =
let open Patterns.Head in
match discr.pat_desc with
| Record _ | Tuple _ | Lazy ->
(* [discr] comes from [discr_pat], and in this case subsumes any of the
patterns we could find on the first column of [rows]. So it is better
to use it for our initial environment than any of the normalized
pattern we might obtain from the first column. *)
[discr,[]]
| _ -> []
in
form_groups initial_constr_group [] rows
in
(* groups are accumulated in reverse order;
we restore the order of rows in the source code *)
let default = List.rev omega_tails in
let constrs =
List.fold_right insert_omega omega_tails constr_groups
|> List.map (fun (discr, rs) -> (discr, List.rev rs))
in
{ default; constrs; }
(* Variant related functions *)
let set_last a =
let rec loop = function
| [] -> assert false
| [_] -> [Patterns.General.erase a]
| x::l -> x :: loop l
in
function
| (_, []) -> (Patterns.Head.deconstruct a, [])
| (first, row) -> (first, loop row)
(* mark constructor lines for failure when they are incomplete *)
let mark_partial =
let zero = make_pat (`Constant (Const_int 0)) Ctype.none Env.empty in
List.map (fun ((hp, _), _ as ps) ->
match hp.pat_desc with
| Patterns.Head.Any -> ps
| _ -> set_last zero ps
)
let close_variant env row =
let Row {fields; more; name=orig_name; closed; fixed} = row_repr row in
let name, static =
List.fold_left
(fun (nm, static) (_tag,f) ->
match row_field_repr f with
| Reither(_, _, false) ->
(* fixed=false means that this tag is not explicitly matched *)
link_row_field_ext ~inside:f rf_absent;
(None, static)
| Reither (_, _, true) -> (nm, false)
| Rabsent | Rpresent _ -> (nm, static))
(orig_name, true) fields in
if not closed || name != orig_name then begin
let more' = if static then Btype.newgenty Tnil else Btype.newgenvar () in
(* this unification cannot fail *)
Ctype.unify env more
(Btype.newgenty
(Tvariant
(create_row ~fields:[] ~more:more'
~closed:true ~name ~fixed)))
end
(*
Check whether the first column of env makes up a complete signature or
not. We work on the discriminating pattern heads of each sub-matrix: they
are not omega/Any.
*)
let full_match closing env = match env with
| [] -> false
| (discr, _) :: _ ->
let open Patterns.Head in
match discr.pat_desc with
| Any -> assert false
| Construct { cstr_tag = Cstr_extension _ ; _ } -> false
| Construct c -> List.length env = c.cstr_consts + c.cstr_nonconsts
| Variant { type_row; _ } ->
let fields =
List.map
(fun (d, _) ->
match d.pat_desc with
| Variant { tag } -> tag
| _ -> assert false)
env
in
let row = type_row () in
if closing && not (Btype.has_fixed_explanation row) then
(* closing=true, we are considering the variant as closed *)
List.for_all
(fun (tag,f) ->
match row_field_repr f with
Rabsent | Reither(_, _, false) -> true
| Reither (_, _, true)
(* m=true, do not discard matched tags, rather warn *)
| Rpresent _ -> List.mem tag fields)
(row_fields row)
else
row_closed row &&
List.for_all
(fun (tag,f) ->
row_field_repr f = Rabsent || List.mem tag fields)
(row_fields row)
| Constant Const_char _ ->
List.length env = 256
| Constant _
| Array _ -> false
| Tuple _
| Record _
| Lazy -> true
(* Written as a non-fragile matching, PR#7451 originated from a fragile matching
below. *)
let should_extend ext env = match ext with
| None -> false
| Some ext -> begin match env with
| [] -> assert false
| (p,_)::_ ->
let open Patterns.Head in
begin match p.pat_desc with
| Construct {cstr_tag=(Cstr_constant _|Cstr_block _|Cstr_unboxed)} ->
let path = get_constructor_type_path p.pat_type p.pat_env in
Path.same path ext
| Construct {cstr_tag=(Cstr_extension _)} -> false
| Constant _ | Tuple _ | Variant _ | Record _ | Array _ | Lazy -> false
| Any -> assert false
end
end
(* build a pattern from a constructor description *)
let pat_of_constr ex_pat cstr =
{ex_pat with pat_desc =
Tpat_construct (mknoloc (Longident.Lident cstr.cstr_name),
cstr, omegas cstr.cstr_arity, None)}
let orify x y = make_pat (Tpat_or (x, y, None)) x.pat_type x.pat_env
let rec orify_many = function
| [] -> assert false
| [x] -> x
| x :: xs -> orify x (orify_many xs)
(* build an or-pattern from a constructor list *)
let pat_of_constrs ex_pat cstrs =
let ex_pat = Patterns.Head.to_omega_pattern ex_pat in
if cstrs = [] then raise Empty else
orify_many (List.map (pat_of_constr ex_pat) cstrs)
let pats_of_type env ty =
match Ctype.extract_concrete_typedecl env ty with
| Typedecl (_, path, {type_kind = Type_variant _ | Type_record _}) ->
begin match Env.find_type_descrs path env with
| Type_variant (cstrs,_) when List.length cstrs <= 1 ||
(* Only explode when all constructors are GADTs *)
List.for_all (fun cd -> cd.cstr_generalized) cstrs ->
List.map (pat_of_constr (make_pat Tpat_any ty env)) cstrs
| Type_record (labels, _) ->
let fields =
List.map (fun ld ->
mknoloc (Longident.Lident ld.lbl_name), ld, omega)
labels
in
[make_pat (Tpat_record (fields, Closed)) ty env]
| _ -> [omega]
end
| Has_no_typedecl ->
begin match get_desc (Ctype.expand_head env ty) with
Ttuple tl ->
[make_pat (Tpat_tuple (List.map (fun (lbl, _) -> lbl, omega) tl))
ty env]
| _ -> [omega]
end
| Typedecl (_, _, {type_kind = Type_abstract _ | Type_open})
| May_have_typedecl -> [omega]
let get_variant_constructors env ty =
match Ctype.extract_concrete_typedecl env ty with
| Typedecl (_, path, {type_kind = Type_variant _}) ->
begin match Env.find_type_descrs path env with
| Type_variant (cstrs,_) -> cstrs
| _ -> fatal_error "Parmatch.get_variant_constructors"
end
| _ -> fatal_error "Parmatch.get_variant_constructors"
module ConstructorSet = Set.Make(struct
type t = constructor_description
let compare c1 c2 = String.compare c1.cstr_name c2.cstr_name
end)
(* Sends back a pattern that complements the given constructors used_constrs *)
let complete_constrs constr used_constrs =
let c = constr.pat_desc in
let constrs = get_variant_constructors constr.pat_env c.cstr_res in
let used_constrs = ConstructorSet.of_list used_constrs in
let others =
List.filter
(fun cnstr -> not (ConstructorSet.mem cnstr used_constrs))
constrs in
(* Split constructors to put constant ones first *)
let const, nonconst =
List.partition (fun cnstr -> cnstr.cstr_arity = 0) others in
const @ nonconst
let build_other_constrs env p =
let open Patterns.Head in
match p.pat_desc with
| Construct ({ cstr_tag = Cstr_extension _ }) -> extra_pat
| Construct
({ cstr_tag = Cstr_constant _ | Cstr_block _ | Cstr_unboxed } as c) ->
let constr = { p with pat_desc = c } in
let get_constr q =
match q.pat_desc with
| Construct c -> c
| _ -> fatal_error "Parmatch.get_constr" in
let used_constrs = List.map (fun (p,_) -> get_constr p) env in
pat_of_constrs p (complete_constrs constr used_constrs)
| _ -> extra_pat
(* Auxiliary for build_other *)
let build_other_constant proj make first next p env =
let all = List.map (fun (p, _) -> proj p.pat_desc) env in
let rec try_const i =
if List.mem i all
then try_const (next i)
else make_pat (make i) p.pat_type p.pat_env
in try_const first
(*
Builds a pattern that is incompatible with all patterns in
the first column of env
*)
let some_private_tag = "<some private tag>"
let build_other ext env =
match env with
| [] -> omega
| (d, _) :: _ ->
let open Patterns.Head in
match d.pat_desc with
| Construct { cstr_tag = Cstr_extension _ } ->
(* let c = {c with cstr_name = "*extension*"} in *) (* PR#7330 *)
make_pat
(Tpat_var (Ident.create_local "*extension*",
{txt="*extension*"; loc = d.pat_loc},
Uid.internal_not_actually_unique))
Ctype.none Env.empty
| Construct _ ->
begin match ext with
| Some ext ->
if Path.same ext (get_constructor_type_path d.pat_type d.pat_env)
then
extra_pat
else
build_other_constrs env d
| _ ->
build_other_constrs env d
end
| Variant { cstr_row; type_row } ->
let tags =
List.map
(fun (d, _) ->
match d.pat_desc with
| Variant { tag } -> tag
| _ -> assert false)
env
in
let make_other_pat tag const =
let arg = if const then None else Some Patterns.omega in
make_pat (Tpat_variant(tag, arg, cstr_row)) d.pat_type d.pat_env
in
let row = type_row () in
begin match
List.fold_left
(fun others (tag,f) ->
if List.mem tag tags then others else
match row_field_repr f with
Rabsent (* | Reither _ *) -> others
(* This one is called after erasing pattern info *)
| Reither (c, _, _) -> make_other_pat tag c :: others
| Rpresent arg -> make_other_pat tag (arg = None) :: others)
[] (row_fields row)
with
[] ->
let tag =
if Btype.has_fixed_explanation row then some_private_tag else
let rec mktag tag =
if List.mem tag tags then mktag (tag ^ "'") else tag in
mktag "AnyOtherTag"
in make_other_pat tag true
| pat::other_pats ->
List.fold_left
(fun p_res pat ->
make_pat (Tpat_or (pat, p_res, None)) d.pat_type d.pat_env)
pat other_pats
end
| Constant Const_char _ ->
let all_chars =
List.map
(fun (p,_) -> match p.pat_desc with
| Constant (Const_char c) -> c
| _ -> assert false)
env
in
let rec find_other i imax =
if i > imax then raise Not_found
else
let ci = Char.chr i in
if List.mem ci all_chars then
find_other (i+1) imax
else
make_pat (Tpat_constant (Const_char ci)) d.pat_type d.pat_env
in
let rec try_chars = function
| [] -> Patterns.omega
| (c1,c2) :: rest ->
try
find_other (Char.code c1) (Char.code c2)
with
| Not_found -> try_chars rest
in
try_chars
[ 'a', 'z' ; 'A', 'Z' ; '0', '9' ;
' ', '~' ; Char.chr 0 , Char.chr 255]
| Constant Const_int _ ->
build_other_constant
(function Constant(Const_int i) -> i | _ -> assert false)
(function i -> Tpat_constant(Const_int i))
0 succ d env
| Constant Const_int32 _ ->
build_other_constant
(function Constant(Const_int32 i) -> i | _ -> assert false)
(function i -> Tpat_constant(Const_int32 i))
0l Int32.succ d env
| Constant Const_int64 _ ->
build_other_constant
(function Constant(Const_int64 i) -> i | _ -> assert false)
(function i -> Tpat_constant(Const_int64 i))
0L Int64.succ d env
| Constant Const_nativeint _ ->
build_other_constant
(function Constant(Const_nativeint i) -> i | _ -> assert false)
(function i -> Tpat_constant(Const_nativeint i))
0n Nativeint.succ d env
| Constant Const_string _ ->
build_other_constant
(function Constant(Const_string (s, _, _)) -> String.length s
| _ -> assert false)
(function i ->
Tpat_constant
(Const_string(String.make i '*',Location.none,None)))
0 succ d env
| Constant Const_float _ ->
build_other_constant
(function Constant(Const_float f) -> float_of_string f
| _ -> assert false)
(function f -> Tpat_constant(Const_float (string_of_float f)))
0.0 (fun f -> f +. 1.0) d env
| Array (am, _) ->
let all_lengths =
List.map
(fun (p,_) -> match p.pat_desc with
| Array (am', len) when am = am' -> len
| _ -> assert false)
env in
let rec try_arrays l =
if List.mem l all_lengths then try_arrays (l+1)
else
make_pat (Tpat_array (am, omegas l)) d.pat_type d.pat_env in
try_arrays 0
| _ -> Patterns.omega
let rec has_instance p = match p.pat_desc with
| Tpat_variant (l,_,r) when is_absent l r -> false
| Tpat_any | Tpat_var _ | Tpat_constant _ | Tpat_variant (_,None,_) -> true
| Tpat_alias (p,_,_,_,_) | Tpat_variant (_,Some p,_) -> has_instance p
| Tpat_or (p1,p2,_) -> has_instance p1 || has_instance p2
| Tpat_construct (_,_,ps,_) | Tpat_array (_, ps) ->
has_instances ps
| Tpat_tuple labeled_ps -> has_instances (List.map snd labeled_ps)
| Tpat_record (lps,_) -> has_instances (List.map (fun (_,_,x) -> x) lps)
| Tpat_lazy p
-> has_instance p
and has_instances = function
| [] -> true
| q::rem -> has_instance q && has_instances rem
(*
Core function :
Is the last row of pattern matrix pss + qs satisfiable ?
That is :
Does there exists at least one value vector, es such that :
1- for all ps in pss ps # es (ps and es are not compatible)
2- qs <= es (es matches qs)
---
In two places in the following function, we check the coherence of the first
column of (pss + qs).
If it is incoherent, then we exit early saying that (pss + qs) is not
satisfiable (which is equivalent to saying "oh, we shouldn't have considered
that branch, no good result came come from here").
But what happens if we have a coherent but ill-typed column?
- we might end up returning [false], which is equivalent to noticing the
incompatibility: clearly this is fine.
- if we end up returning [true] then we're saying that [qs] is useful while
it is not. This is sad but not the end of the world, we're just allowing dead
code to survive.
*)
let rec satisfiable pss qs = match pss with
| [] -> has_instances qs
| _ ->
match qs with
| [] -> false
| q::qs ->
match Patterns.General.(view q |> strip_vars).pat_desc with
| `Or(q1,q2,_) ->
satisfiable pss (q1::qs) || satisfiable pss (q2::qs)
| `Any ->
let pss = simplify_first_col pss in
if not (all_coherent (first_column pss)) then
false
else begin
let { default; constrs } =
let q0 = discr_pat Patterns.Simple.omega pss in
build_specialized_submatrices ~extend_row:(@) q0 pss in
if not (full_match false constrs) then
satisfiable default qs
else
List.exists
(fun (p,pss) ->
not (is_absent_pat p) &&
satisfiable pss
(simple_match_args p Patterns.Head.omega [] @ qs))
constrs
end
| `Variant (l,_,r) when is_absent l r -> false
| #Patterns.Simple.view as view ->
let q = { q with pat_desc = view } in
let pss = simplify_first_col pss in
let hq, qargs = Patterns.Head.deconstruct q in
if not (all_coherent (hq :: first_column pss)) then
false
else begin
let q0 = discr_pat q pss in
satisfiable (build_specialized_submatrix ~extend_row:(@) q0 pss)
(simple_match_args q0 hq qargs @ qs)
end
(* While [satisfiable] only checks whether the last row of [pss + qs] is
satisfiable, this function returns the (possibly empty) list of vectors [es]
which verify:
1- for all ps in pss, ps # es (ps and es are not compatible)
2- qs <= es (es matches qs)
This is done to enable GADT handling
For considerations regarding the coherence check, see the comment on
[satisfiable] above. *)
let rec list_satisfying_vectors pss qs =
match pss with
| [] -> if has_instances qs then [qs] else []
| _ ->
match qs with
| [] -> []
| q :: qs ->
match Patterns.General.(view q |> strip_vars).pat_desc with
| `Or(q1,q2,_) ->
list_satisfying_vectors pss (q1::qs) @
list_satisfying_vectors pss (q2::qs)
| `Any ->
let pss = simplify_first_col pss in
if not (all_coherent (first_column pss)) then
[]
else begin
let q0 = discr_pat Patterns.Simple.omega pss in
let wild default_matrix p =
List.map (fun qs -> p::qs)
(list_satisfying_vectors default_matrix qs)
in
match build_specialized_submatrices ~extend_row:(@) q0 pss with
| { default; constrs = [] } ->
(* first column of pss is made of variables only *)
wild default omega
| { default; constrs = ((p,_)::_ as constrs) } ->
let for_constrs () =
List.flatten (
List.map (fun (p,pss) ->
if is_absent_pat p then
[]
else
let witnesses =
list_satisfying_vectors pss
(simple_match_args p Patterns.Head.omega [] @ qs)
in
let p = Patterns.Head.to_omega_pattern p in
List.map (set_args p) witnesses
) constrs
)
in
if full_match false constrs then for_constrs () else
begin match p.pat_desc with
| Construct _ ->
(* activate this code
for checking non-gadt constructors *)
wild default (build_other_constrs constrs p)
@ for_constrs ()
| _ ->
wild default Patterns.omega
end
end
| `Variant (l, _, r) when is_absent l r -> []
| #Patterns.Simple.view as view ->
let q = { q with pat_desc = view } in
let hq, qargs = Patterns.Head.deconstruct q in
let pss = simplify_first_col pss in
if not (all_coherent (hq :: first_column pss)) then
[]
else begin
let q0 = discr_pat q pss in
List.map (set_args (Patterns.Head.to_omega_pattern q0))
(list_satisfying_vectors
(build_specialized_submatrix ~extend_row:(@) q0 pss)
(simple_match_args q0 hq qargs @ qs))
end
(******************************************)
(* Look for a row that matches some value *)
(******************************************)
(*
Useful for seeing if the example of
non-matched value can indeed be matched
(by a guarded clause)
*)
let rec do_match pss qs = match qs with
| [] ->
begin match pss with
| []::_ -> true
| _ -> false
end
| q::qs -> match Patterns.General.(view q |> strip_vars).pat_desc with
| `Or (q1,q2,_) ->
do_match pss (q1::qs) || do_match pss (q2::qs)
| `Any ->
let rec remove_first_column = function
| (_::ps)::rem -> ps::remove_first_column rem
| _ -> []
in
do_match (remove_first_column pss) qs
| #Patterns.Simple.view as view ->
let q = { q with pat_desc = view } in
let q0, qargs = Patterns.Head.deconstruct q in
let pss = simplify_first_col pss in
(* [pss] will (or won't) match [q0 :: qs] regardless of the coherence of
its first column. *)
do_match
(build_specialized_submatrix ~extend_row:(@) q0 pss)
(qargs @ qs)
(*
let print_pat pat =
let rec string_of_pat pat =
match pat.pat_desc with
Tpat_var _ -> "v"
| Tpat_any -> "_"
| Tpat_alias (p, x) -> Printf.sprintf "(%s) as ?" (string_of_pat p)
| Tpat_constant n -> "0"
| Tpat_construct (_, lid, _) ->
Printf.sprintf "%s" (String.concat "." (Longident.flatten lid.txt))
| Tpat_lazy p ->
Printf.sprintf "(lazy %s)" (string_of_pat p)
| Tpat_or (p1,p2,_) ->
Printf.sprintf "(%s | %s)" (string_of_pat p1) (string_of_pat p2)
| Tpat_tuple list ->
Printf.sprintf "(%s)" (String.concat "," (List.map string_of_pat list))
| Tpat_variant (_, _, _) -> "variant"
| Tpat_record (_, _) -> "record"
| Tpat_array (Mutable, _) -> "array"
| Tpat_array (Immutable, _) -> "immutable array"
in
Printf.fprintf stderr "PAT[%s]\n%!" (string_of_pat pat)
*)
(*
Now another satisfiable function that additionally
supplies an example of a matching value.
This function should be called for exhaustiveness check only.
*)
let rec exhaust (ext:Path.t option) pss n = match pss with
| [] -> Seq.return (omegas n)
| []::_ -> Seq.empty
| [(p :: ps)] -> exhaust_single_row ext p ps n
| pss -> specialize_and_exhaust ext pss n
and exhaust_single_row ext p ps n =
(* Shortcut: in the single-row case p :: ps we know that all
counter-examples are either of the form
counter-example(p) :: omegas
or
p :: counter-examples(ps)
This is very interesting in the case where p contains
or-patterns, as the non-shortcut path below would do a separate
search for each constructor of the or-pattern, which can lead to
an exponential blowup on examples such as
| (A|B), (A|B), (A|B), (A|B) -> foo
Note that this shortcut also applies to examples such as
| A, A, A, A -> foo | (A|B), (A|B), (A|B), (A|B) -> bar
thanks to the [get_mins] preprocessing step which will drop the
first row (subsumed by the second). Code with this shape does
occur naturally when people want to avoid fragile pattern
matches: if A and B are the only two constructors, this is the
best way to make a non-fragile distinction between "all As" and
"at least one B".
*)
List.to_seq [Some p; None] |> Seq.flat_map
(function
| Some p ->
let sub_witnesses = exhaust ext [ps] (n - 1) in
Seq.map (fun row -> p :: row) sub_witnesses
| None ->
(* note: calling [exhaust] recursively of p would
result in an infinite loop in the case n=1 *)
let p_witnesses = specialize_and_exhaust ext [[p]] 1 in
Seq.map (fun p_row -> p_row @ omegas (n - 1)) p_witnesses
)
and specialize_and_exhaust ext pss n =
let pss = simplify_first_col pss in
if not (all_coherent (first_column pss)) then
(* We're considering an ill-typed branch, we won't actually be able to
produce a well typed value taking that branch. *)
Seq.empty
else begin
(* Assuming the first column is ill-typed but considered coherent, we
might end up producing an ill-typed witness of non-exhaustivity
corresponding to the current branch.
If [exhaust] has been called by [do_check_partial], then the witnesses
produced get typechecked and the ill-typed ones are discarded.
If [exhaust] has been called by [do_check_fragile], then it is possible
we might fail to warn the user that the matching is fragile. See for
example testsuite/tests/warnings/w04_failure.ml. *)
let q0 = discr_pat Patterns.Simple.omega pss in
match build_specialized_submatrices ~extend_row:(@) q0 pss with
| { default; constrs = [] } ->
(* first column of pss is made of variables only *)
let sub_witnesses = exhaust ext default (n-1) in
let q0 = Patterns.Head.to_omega_pattern q0 in
Seq.map (fun row -> q0::row) sub_witnesses
| { default; constrs } ->
let try_non_omega (p,pss) =
if is_absent_pat p then
Seq.empty
else
let sub_witnesses =
exhaust
ext pss
(List.length (simple_match_args p Patterns.Head.omega [])
+ n - 1)
in
let p = Patterns.Head.to_omega_pattern p in
Seq.map (set_args p) sub_witnesses
in
let try_omega () =
if full_match false constrs && not (should_extend ext constrs) then
Seq.empty
else
let sub_witnesses = exhaust ext default (n-1) in
match build_other ext constrs with
| exception Empty ->
(* cannot occur, since constructors don't make
a full signature *)
fatal_error "Parmatch.exhaust"
| p ->
Seq.map (fun tail -> p :: tail) sub_witnesses
in
(* Lazily compute witnesses for all constructor submatrices
(Some constr_mat) then the wildcard/default submatrix (None).
Note that the call to [try_omega ()] is delayed to after
all constructor matrices have been traversed. *)
List.map (fun constr_mat -> Some constr_mat) constrs @ [None]
|> List.to_seq
|> Seq.flat_map
(function
| Some constr_mat -> try_non_omega constr_mat
| None -> try_omega ())
end
let exhaust ext pss n =
exhaust ext pss n
|> Seq.map (function
| [x] -> x
| _ -> assert false)
(*
Another exhaustiveness check, enforcing variant typing.
Note that it does not check exact exhaustiveness, but whether a
matching could be made exhaustive by closing all variant types.
When this is true of all other columns, the current column is left
open (even if it means that the whole matching is not exhaustive as
a result).
When this is false for the matrix minus the current column, and the
current column is composed of variant tags, we close the variant
(even if it doesn't help in making the matching exhaustive).
*)
let rec pressure_variants tdefs = function
| [] -> false
| []::_ -> true
| pss ->
let pss = simplify_first_col pss in
if not (all_coherent (first_column pss)) then
true
else begin
let q0 = discr_pat Patterns.Simple.omega pss in
match build_specialized_submatrices ~extend_row:(@) q0 pss with
| { default; constrs = [] } -> pressure_variants tdefs default
| { default; constrs } ->
let rec try_non_omega = function
| (_p,pss) :: rem ->
let ok = pressure_variants tdefs pss in
(* The order below matters : we want [pressure_variants] to be
called on all the specialized submatrices because we might
close some variant in any of them regardless of whether [ok]
is true for [pss] or not *)
try_non_omega rem && ok
| [] -> true
in
if full_match (tdefs=None) constrs then
try_non_omega constrs
else if tdefs = None then
pressure_variants None default
else
let full = full_match true constrs in
let ok =
if full then
try_non_omega constrs
else begin
let { constrs = partial_constrs; _ } =
build_specialized_submatrices ~extend_row:(@) q0
(mark_partial pss)
in
try_non_omega partial_constrs
end
in
begin match constrs, tdefs with
| [], _
| _, None -> ()
| (d, _) :: _, Some env ->
match d.pat_desc with
| Variant { type_row; _ } ->
let row = type_row () in
if Btype.has_fixed_explanation row
|| pressure_variants None default then ()
else close_variant env row
| _ -> ()
end;
ok
end
(* Yet another satisfiable function *)
(*
This time every_satisfiable pss qs checks the
utility of every expansion of qs.
Expansion means expansion of or-patterns inside qs
*)
type answer =
| Used (* Useful pattern *)
| Unused (* Useless pattern *)
| Upartial of Typedtree.pattern list (* Mixed, with list of useless ones *)
(* this row type enable column processing inside the matrix
- left -> elements not to be processed,
- right -> elements to be processed
*)
type usefulness_row =
{no_ors : pattern list ; ors : pattern list ; active : pattern list}
(*
let pretty_row {ors=ors ; no_ors=no_ors; active=active} =
pretty_line ors ; prerr_string " *" ;
pretty_line no_ors ; prerr_string " *" ;
pretty_line active
let pretty_rows rs =
prerr_endline "begin matrix" ;
List.iter
(fun r ->
pretty_row r ;
prerr_endline "")
rs ;
prerr_endline "end matrix"
*)
(* Initial build *)
let make_row ps = {ors=[] ; no_ors=[]; active=ps}
let make_rows pss = List.map make_row pss
(* Useful to detect and expand or pats inside as pats *)
let is_var p = match Patterns.General.(view p |> strip_vars).pat_desc with
| `Any -> true
| _ -> false
let is_var_column rs =
List.for_all
(fun r -> match r.active with
| p::_ -> is_var p
| [] -> assert false)
rs
(* Standard or-args for left-to-right matching *)
let rec or_args p = match p.pat_desc with
| Tpat_or (p1,p2,_) -> p1,p2
| Tpat_alias (p,_,_,_,_) -> or_args p
| _ -> assert false
(* Just remove current column *)
let remove r = match r.active with
| _::rem -> {r with active=rem}
| [] -> assert false
let remove_column rs = List.map remove rs
(* Current column has been processed *)
let push_no_or r = match r.active with
| p::rem -> { r with no_ors = p::r.no_ors ; active=rem}
| [] -> assert false
let push_or r = match r.active with
| p::rem -> { r with ors = p::r.ors ; active=rem}
| [] -> assert false
let push_or_column rs = List.map push_or rs
and push_no_or_column rs = List.map push_no_or rs
let rec simplify_first_usefulness_col = function
| [] -> []
| row :: rows ->
match row.active with
| [] -> assert false (* the rows are non-empty! *)
| p :: ps ->
let add_column p ps k =
(p, { row with active = ps }) :: k in
simplify_head_pat ~add_column p ps
(simplify_first_usefulness_col rows)
(* Back to normal matrices *)
let make_vector r = List.rev r.no_ors
let make_matrix rs = List.map make_vector rs
(* Standard union on answers *)
let union_res r1 r2 = match r1, r2 with
| (Unused,_)
| (_, Unused) -> Unused
| Used,_ -> r2
| _, Used -> r1
| Upartial u1, Upartial u2 -> Upartial (u1@u2)
(* propose or pats for expansion *)
let extract_elements qs =
let rec do_rec seen = function
| [] -> []
| q::rem ->
{no_ors= List.rev_append seen rem @ qs.no_ors ;
ors=[] ;
active = [q]}::
do_rec (q::seen) rem in
do_rec [] qs.ors
(* idem for matrices *)
let transpose rs = match rs with
| [] -> assert false
| r::rem ->
let i = List.map (fun x -> [x]) r in
List.fold_left
(List.map2 (fun r x -> x::r))
i rem
let extract_columns pss qs = match pss with
| [] -> List.map (fun _ -> []) qs.ors
| _ ->
let rows = List.map extract_elements pss in
transpose rows
(* Core function
The idea is to first look for or patterns (recursive case), then
check or-patterns argument usefulness (terminal case)
*)
let rec every_satisfiables pss qs = match qs.active with
| [] ->
(* qs is now partitioned, check usefulness *)
begin match qs.ors with
| [] -> (* no or-patterns *)
if satisfiable (make_matrix pss) (make_vector qs) then
Used
else
Unused
| _ -> (* n or-patterns -> 2n expansions *)
List.fold_right2
(fun pss qs r -> match r with
| Unused -> Unused
| _ ->
match qs.active with
| [q] ->
let q1,q2 = or_args q in
let r_loc = every_both pss qs q1 q2 in
union_res r r_loc
| _ -> assert false)
(extract_columns pss qs) (extract_elements qs)
Used
end
| q::rem ->
begin match Patterns.General.(view q |> strip_vars).pat_desc with
| `Any ->
if is_var_column pss then
(* forget about ``all-variable'' columns now *)
every_satisfiables (remove_column pss) (remove qs)
else
(* otherwise this is direct food for satisfiable *)
every_satisfiables (push_no_or_column pss) (push_no_or qs)
| `Or (q1,q2,_) ->
if
q1.pat_loc.Location.loc_ghost &&
q2.pat_loc.Location.loc_ghost
then
(* syntactically generated or-pats should not be expanded *)
every_satisfiables (push_no_or_column pss) (push_no_or qs)
else
(* this is a real or-pattern *)
every_satisfiables (push_or_column pss) (push_or qs)
| `Variant (l,_,r) when is_absent l r -> (* Ah Jacques... *)
Unused
| #Patterns.Simple.view as view ->
let q = { q with pat_desc = view } in
(* standard case, filter matrix *)
let pss = simplify_first_usefulness_col pss in
let hq, args = Patterns.Head.deconstruct q in
(* The handling of incoherent matrices is kept in line with
[satisfiable] *)
if not (all_coherent (hq :: first_column pss)) then
Unused
else begin
let q0 = discr_pat q pss in
every_satisfiables
(build_specialized_submatrix q0 pss
~extend_row:(fun ps r -> { r with active = ps @ r.active }))
{qs with active=simple_match_args q0 hq args @ rem}
end
end
(*
This function ``every_both'' performs the usefulness check
of or-pat q1|q2.
The trick is to call every_satisfied twice with
current active columns restricted to q1 and q2,
That way,
- others orpats in qs.ors will not get expanded.
- all matching work performed on qs.no_ors is not performed again.
*)
and every_both pss qs q1 q2 =
let qs1 = {qs with active=[q1]}
and qs2 = {qs with active=[q2]} in
let r1 = every_satisfiables pss qs1
and r2 = every_satisfiables (if compat q1 q2 then qs1::pss else pss) qs2 in
match r1 with
| Unused ->
begin match r2 with
| Unused -> Unused
| Used -> Upartial [q1]
| Upartial u2 -> Upartial (q1::u2)
end
| Used ->
begin match r2 with
| Unused -> Upartial [q2]
| _ -> r2
end
| Upartial u1 ->
begin match r2 with
| Unused -> Upartial (u1@[q2])
| Used -> r1
| Upartial u2 -> Upartial (u1 @ u2)
end
(* le_pat p q means, forall V, V matches q implies V matches p *)
let rec le_pat p q =
match (p.pat_desc, q.pat_desc) with
| (Tpat_var _|Tpat_any),_ -> true
| Tpat_alias(p,_,_,_,_), _ -> le_pat p q
| _, Tpat_alias(q,_,_,_,_) -> le_pat p q
| Tpat_constant(c1), Tpat_constant(c2) -> const_compare c1 c2 = 0
| Tpat_construct(_,c1,ps,_), Tpat_construct(_,c2,qs,_) ->
Data_types.equal_constr c1 c2 && le_pats ps qs
| Tpat_variant(l1,Some p1,_), Tpat_variant(l2,Some p2,_) ->
(l1 = l2 && le_pat p1 p2)
| Tpat_variant(l1,None,_r1), Tpat_variant(l2,None,_) ->
l1 = l2
| Tpat_variant(_,_,_), Tpat_variant(_,_,_) -> false
| Tpat_tuple(labeled_ps), Tpat_tuple(labeled_qs) ->
le_tuple_pats labeled_ps labeled_qs
| Tpat_lazy p, Tpat_lazy q -> le_pat p q
| Tpat_record (l1,_), Tpat_record (l2,_) ->
let ps,qs = records_args l1 l2 in
le_pats ps qs
| Tpat_array(am1, ps), Tpat_array(am2, qs) ->
am1 = am2 && List.length ps = List.length qs && le_pats ps qs
(* In all other cases, enumeration is performed *)
| _,_ -> not (satisfiable [[p]] [q])
and le_pats ps qs =
match ps,qs with
p::ps, q::qs -> le_pat p q && le_pats ps qs
| _, _ -> true
and le_tuple_pats labeled_ps labeled_qs =
match labeled_ps, labeled_qs with
(p_label, p)::labeled_ps, (q_label, q)::labeled_qs ->
Option.equal String.equal p_label q_label
&& le_pat p q && le_tuple_pats labeled_ps labeled_qs
| _, _ -> true
let get_mins le ps =
let rec select_rec r = function
[] -> r
| p::ps ->
if List.exists (fun p0 -> le p0 p) ps
then select_rec r ps
else select_rec (p::r) ps in
(* [select_rec] removes the elements that are followed by a smaller element.
An element that is preceded by a smaller element may stay in the list.
We thus do two passes on the list, which is returned reversed
the first time. *)
select_rec [] (select_rec [] ps)
(*
lub p q is a pattern that matches all values matched by p and q
may raise Empty, when p and q are not compatible
*)
let rec lub p q = match p.pat_desc,q.pat_desc with
| Tpat_alias (p,_,_,_,_),_ -> lub p q
| _,Tpat_alias (q,_,_,_,_) -> lub p q
| (Tpat_any|Tpat_var _),_ -> q
| _,(Tpat_any|Tpat_var _) -> p
| Tpat_or (p1,p2,_),_ -> orlub p1 p2 q
| _,Tpat_or (q1,q2,_) -> orlub q1 q2 p (* Thanks god, lub is commutative *)
| Tpat_constant c1, Tpat_constant c2 when const_compare c1 c2 = 0 -> p
| Tpat_tuple ps, Tpat_tuple qs ->
let rs = tuple_lubs ps qs in
make_pat (Tpat_tuple rs) p.pat_type p.pat_env
| Tpat_lazy p, Tpat_lazy q ->
let r = lub p q in
make_pat (Tpat_lazy r) p.pat_type p.pat_env
| Tpat_construct (lid,c1,ps1,_), Tpat_construct (_,c2,ps2,_)
when Data_types.equal_constr c1 c2 ->
let rs = lubs ps1 ps2 in
make_pat (Tpat_construct (lid, c1, rs, None))
p.pat_type p.pat_env
| Tpat_variant(l1,Some p1,row), Tpat_variant(l2,Some p2,_)
when l1=l2 ->
let r=lub p1 p2 in
make_pat (Tpat_variant (l1,Some r,row)) p.pat_type p.pat_env
| Tpat_variant (l1,None,_row), Tpat_variant(l2,None,_)
when l1 = l2 -> p
| Tpat_record (l1,closed),Tpat_record (l2,_) ->
let rs = record_lubs l1 l2 in
make_pat (Tpat_record (rs, closed)) p.pat_type p.pat_env
| Tpat_array (am1, ps), Tpat_array (am2, qs)
when am1 = am2 && List.length ps = List.length qs ->
let rs = lubs ps qs in
make_pat (Tpat_array (am1, rs)) p.pat_type p.pat_env
| _,_ ->
raise Empty
and orlub p1 p2 q =
try
let r1 = lub p1 q in
try
{q with pat_desc=(Tpat_or (r1,lub p2 q,None))}
with
| Empty -> r1
with
| Empty -> lub p2 q
and record_lubs l1 l2 =
let rec lub_rec l1 l2 = match l1,l2 with
| [],_ -> l2
| _,[] -> l1
| (lid1, lbl1,p1)::rem1, (lid2, lbl2,p2)::rem2 ->
if lbl1.lbl_pos < lbl2.lbl_pos then
(lid1, lbl1,p1)::lub_rec rem1 l2
else if lbl2.lbl_pos < lbl1.lbl_pos then
(lid2, lbl2,p2)::lub_rec l1 rem2
else
(lid1, lbl1,lub p1 p2)::lub_rec rem1 rem2 in
lub_rec l1 l2
and tuple_lubs ps qs = match ps,qs with
| [], [] -> []
| (p_label, p)::ps, (q_label, q)::qs
when Option.equal String.equal p_label q_label ->
(p_label, lub p q) :: tuple_lubs ps qs
| _,_ -> raise Empty
and lubs ps qs = match ps,qs with
| p::ps, q::qs -> lub p q :: lubs ps qs
| _,_ -> []
(******************************)
(* Exported variant closing *)
(******************************)
(* Apply pressure to variants *)
let pressure_variants tdefs patl =
ignore (pressure_variants
(Some tdefs)
(List.map (fun p -> [p; omega]) patl))
let pressure_variants_in_computation_pattern tdefs patl =
let add_row pss p_opt =
match p_opt with
| None -> pss
| Some p -> p :: pss
in
let val_pss, exn_pss =
List.fold_right (fun pat (vpss, epss)->
let (vp, ep) = split_pattern pat in
add_row vpss vp, add_row epss ep
) patl ([], [])
in
pressure_variants tdefs val_pss;
pressure_variants tdefs exn_pss
(*****************************)
(* Utilities for diagnostics *)
(*****************************)
(*
Build up a working pattern matrix by forgetting
about guarded patterns
*)
let rec initial_matrix = function
[] -> []
| {has_guard=true} :: rem -> initial_matrix rem
| {has_guard=false; pattern=p} :: rem -> [p] :: initial_matrix rem
(*
Build up a working pattern matrix by keeping
only the patterns which are guarded
*)
let rec initial_only_guarded = function
| [] -> []
| { has_guard = false; _} :: rem ->
initial_only_guarded rem
| { pattern = pat; _ } :: rem ->
[pat] :: initial_only_guarded rem
(************************)
(* Exhaustiveness check *)
(************************)
(* Whether the counter-example contains an extension pattern *)
let contains_extension pat =
exists_pattern
(function
| {pat_desc=Tpat_var (_, {txt="*extension*"}, _)} -> true
| _ -> false)
pat
let do_check_partial ~pred loc casel pss = match pss with
| [] ->
(*
This can occur
- For empty matches generated by ocamlp4 (no warning)
- when all patterns have guards (then, casel <> [])
(specific warning)
Then match MUST be considered non-exhaustive,
otherwise compilation of PM is broken.
*)
begin match casel with
| [] -> ()
| _ ->
if Warnings.is_active Warnings.All_clauses_guarded then
Location.prerr_warning loc Warnings.All_clauses_guarded
end ;
Partial
| ps::_ ->
let counter_examples =
exhaust None pss (List.length ps) |> Seq.filter_map pred in
match counter_examples () with
| Seq.Nil -> Total
| Seq.Cons (v, _rest) ->
if Warnings.is_active (Warnings.Partial_match Format_doc.Doc.empty) then
begin
let errmsg =
let doc = ref Format_doc.Doc.empty in
let fmt = Format_doc.formatter doc in
Format_doc.fprintf fmt "@[<v>%a"
(Misc.Style.as_inline_code Printpat.top_pretty) v;
if do_match (initial_only_guarded casel) [v] then
Format_doc.fprintf fmt
"@,(However, some guarded clause may match this value.)";
if contains_extension v then
Format_doc.fprintf fmt
"@,@[Matching over values of extensible variant types \
(the *extension* above)@,\
must include a wild card pattern@ in order to be exhaustive.@]"
;
Format_doc.fprintf fmt "@]";
!doc
in
Location.prerr_warning loc (Warnings.Partial_match errmsg)
end;
Partial
(*****************)
(* Fragile check *)
(*****************)
(* Collect all data types in a pattern *)
let rec add_path path = function
| [] -> [path]
| x::rem as paths ->
if Path.same path x then paths
else x::add_path path rem
let extendable_path path =
not
(Path.same path Predef.path_bool ||
Path.same path Predef.path_list ||
Path.same path Predef.path_unit ||
Path.same path Predef.path_option)
let rec collect_paths_from_pat r p = match p.pat_desc with
| Tpat_construct(_, {cstr_tag=(Cstr_constant _|Cstr_block _|Cstr_unboxed)},
ps, _) ->
let path = get_constructor_type_path p.pat_type p.pat_env in
List.fold_left
collect_paths_from_pat
(if extendable_path path then add_path path r else r)
ps
| Tpat_any|Tpat_var _|Tpat_constant _| Tpat_variant (_,None,_) -> r
| Tpat_tuple ps ->
List.fold_left (fun r (_, p) -> collect_paths_from_pat r p) r ps
| Tpat_array (_, ps) | Tpat_construct (_, {cstr_tag=Cstr_extension _}, ps, _)->
List.fold_left collect_paths_from_pat r ps
| Tpat_record (lps,_) ->
List.fold_left
(fun r (_, _, p) -> collect_paths_from_pat r p)
r lps
| Tpat_variant (_, Some p, _) | Tpat_alias (p,_,_,_,_) ->
collect_paths_from_pat r p
| Tpat_or (p1,p2,_) ->
collect_paths_from_pat (collect_paths_from_pat r p1) p2
| Tpat_lazy p
->
collect_paths_from_pat r p
(*
Actual fragile check
1. Collect data types in the patterns of the match.
2. One exhaustivity check per datatype, considering that
the type is extended.
*)
let do_check_fragile loc casel pss =
let exts =
List.fold_left
(fun r c -> collect_paths_from_pat r c.pattern)
[] casel in
match exts with
| [] -> ()
| _ -> match pss with
| [] -> ()
| ps::_ ->
List.iter
(fun ext ->
let witnesses = exhaust (Some ext) pss (List.length ps) in
match witnesses () with
| Seq.Nil ->
Location.prerr_warning
loc
(Warnings.Fragile_match (Path.name ext))
| Seq.Cons _ -> ())
exts
(********************************)
(* Exported unused clause check *)
(********************************)
let check_unused pred casel =
if Warnings.is_active Warnings.Redundant_case
|| List.exists (fun vc -> vc.needs_refute) casel then
let rec do_rec pref = function
| [] -> ()
| {pattern=q; has_guard; needs_refute=refute} :: rem ->
let qs = [q] in
begin try
let pss =
(* prev was accumulated in reverse order;
restore source order to get ordered counter-examples *)
List.rev pref
|> List.filter (compats qs)
|> get_mins le_pats in
(* First look for redundant or partially redundant patterns *)
let r = every_satisfiables (make_rows pss) (make_row qs) in
(* Do not warn for unused [pat -> .] *)
if r = Unused && refute then () else
let r =
(* Do not refine if either:
- we already know the clause is unused
- the clause under consideration is not a refutation clause
and either:
+ there are no other lines
+ we do not care whether the types prevent this clause to
be reached.
If the clause under consideration *is* a refutation clause
then we do need to check more carefully whether it can be
refuted or not. *)
let skip =
r = Unused || (not refute && pref = []) ||
not(refute || Warnings.is_active Warnings.Unreachable_case) in
if skip then r else
(* Then look for empty patterns *)
let sfs = list_satisfying_vectors pss qs in
if sfs = [] then Unused else
let sfs =
List.map (function [u] -> u | _ -> assert false) sfs in
let u = orify_many sfs in
(*Format.eprintf "%a@." pretty_val u;*)
let pattern = {u with pat_loc = q.pat_loc} in
match pred refute pattern with
None when not refute ->
Location.prerr_warning q.pat_loc Warnings.Unreachable_case;
Used
| _ -> r
in
match r with
| Unused ->
Location.prerr_warning
q.pat_loc Warnings.Redundant_case
| Upartial ps ->
List.iter
(fun p ->
Location.prerr_warning
p.pat_loc Warnings.Redundant_subpat)
ps
| Used -> ()
with Empty | Not_found -> assert false
end ;
if has_guard then
do_rec pref rem
else
do_rec ([q]::pref) rem in
do_rec [] casel
(*********************************)
(* Exported irrefutability tests *)
(*********************************)
let irrefutable pat = le_pat pat omega
let inactive ~partial pat =
match partial with
| Partial -> false
| Total -> begin
let rec loop pat =
match pat.pat_desc with
| Tpat_lazy _ | Tpat_array (Mutable, _) ->
false
| Tpat_any | Tpat_var _ | Tpat_variant (_, None, _) ->
true
| Tpat_constant c -> begin
match c with
| Const_string _
| Const_int _ | Const_char _ | Const_float _
| Const_int32 _ | Const_int64 _ | Const_nativeint _ -> true
end
| Tpat_tuple ps ->
List.for_all (fun (_,p) -> loop p) ps
| Tpat_construct (_, _, ps, _) | Tpat_array (Immutable, ps) ->
List.for_all (fun p -> loop p) ps
| Tpat_alias (p,_,_,_,_) | Tpat_variant (_, Some p, _) ->
loop p
| Tpat_record (ldps,_) ->
List.for_all
(fun (_, lbl, p) -> lbl.lbl_mut = Immutable && loop p)
ldps
| Tpat_or (p,q,_) ->
loop p && loop q
in
loop pat
end
(*********************************)
(* Exported exhaustiveness check *)
(*********************************)
(*
Fragile check is performed when required and
on exhaustive matches only.
*)
let check_partial pred loc casel =
let pss = initial_matrix casel in
let pss = get_mins le_pats pss in
let total = do_check_partial ~pred loc casel pss in
if
total = Total && Warnings.is_active (Warnings.Fragile_match "")
then begin
do_check_fragile loc casel pss
end ;
total
(*************************************)
(* Ambiguous variable in or-patterns *)
(*************************************)
(* Specification: ambiguous variables in or-patterns.
The semantics of or-patterns in OCaml is specified with
a left-to-right bias: a value [v] matches the pattern [p | q] if it
matches [p] or [q], but if it matches both, the environment
captured by the match is the environment captured by [p], never the
one captured by [q].
While this property is generally well-understood, one specific case
where users expect a different semantics is when a pattern is
followed by a when-guard: [| p when g -> e]. Consider for example:
| ((Const x, _) | (_, Const x)) when is_neutral x -> branch
The semantics is clear: match the scrutinee against the pattern, if
it matches, test the guard, and if the guard passes, take the
branch.
However, consider the input [(Const a, Const b)], where [a] fails
the test [is_neutral f], while [b] passes the test [is_neutral
b]. With the left-to-right semantics, the clause above is *not*
taken by its input: matching [(Const a, Const b)] against the
or-pattern succeeds in the left branch, it returns the environment
[x -> a], and then the guard [is_neutral a] is tested and fails,
the branch is not taken. Most users, however, intuitively expect
that any pair that has one side passing the test will take the
branch. They assume it is equivalent to the following:
| (Const x, _) when is_neutral x -> branch
| (_, Const x) when is_neutral x -> branch
while it is not.
The code below is dedicated to finding these confusing cases: the
cases where a guard uses "ambiguous" variables, that are bound to
different parts of the scrutinees by different sides of
a or-pattern. In other words, it finds the cases where the
specified left-to-right semantics is not equivalent to
a non-deterministic semantics (any branch can be taken) relatively
to a specific guard.
*)
let pattern_vars p = Ident.Set.of_list (Typedtree.pat_bound_idents p)
(* Row for ambiguous variable search,
row is the traditional pattern row,
varsets contain a list of head variable sets (varsets)
A given varset contains all the variables that appeared at the head
of a pattern in the row at some point during traversal: they would
all be bound to the same value at matching time. On the contrary,
two variables of different varsets appeared at different places in
the pattern and may be bound to distinct sub-parts of the matched
value.
All rows of a (sub)matrix have rows of the same length,
but also varsets of the same length.
Varsets are populated when simplifying the first column
-- the variables of the head pattern are collected in a new varset.
For example,
{ row = x :: r1; varsets = s1 }
{ row = (Some _) as y :: r2; varsets = s2 }
{ row = (None as x) as y :: r3; varsets = s3 }
{ row = (Some x | (None as x)) :: r4 with varsets = s4 }
becomes
(_, { row = r1; varsets = {x} :: s1 })
(Some _, { row = r2; varsets = {y} :: s2 })
(None, { row = r3; varsets = {x, y} :: s3 })
(Some x, { row = r4; varsets = {} :: s4 })
(None, { row = r4; varsets = {x} :: s4 })
*)
type amb_row = { row : pattern list ; varsets : Ident.Set.t list; }
let simplify_head_amb_pat head_bound_variables varsets ~add_column p ps k =
let rec simpl head_bound_variables varsets p ps k =
match (Patterns.General.view p).pat_desc with
| `Alias (p,x,_,_,_) ->
simpl (Ident.Set.add x head_bound_variables) varsets p ps k
| `Var (x,_,_) ->
simpl (Ident.Set.add x head_bound_variables) varsets Patterns.omega ps k
| `Or (p1,p2,_) ->
simpl head_bound_variables varsets p1 ps
(simpl head_bound_variables varsets p2 ps k)
| #Patterns.Simple.view as view ->
add_column (Patterns.Head.deconstruct { p with pat_desc = view })
{ row = ps; varsets = head_bound_variables :: varsets; } k
in simpl head_bound_variables varsets p ps k
(*
To accurately report ambiguous variables, one must consider
that previous clauses have already matched some values.
Consider for example:
| (Foo x, Foo y) -> ...
| ((Foo x, _) | (_, Foo x)) when bar x -> ...
The second line taken in isolation uses an unstable variable,
but the discriminating values, of the shape [(Foo v1, Foo v2)],
would all be filtered by the line above.
To track this information, the matrices we analyze contain both
*positive* rows, that describe the rows currently being analyzed
(of type Varsets.row, so that their varsets are tracked) and
*negative rows*, that describe the cases already matched against.
The values matched by a signed matrix are the values matched by
some of the positive rows but none of the negative rows. In
particular, a variable is stable if, for any value not matched by
any of the negative rows, the environment captured by any of the
matching positive rows is identical.
*)
type ('a, 'b) signed = Positive of 'a | Negative of 'b
let rec simplify_first_amb_col = function
| [] -> []
| (Negative [] | Positive { row = []; _ }) :: _ -> assert false
| Negative (n :: ns) :: rem ->
let add_column n ns k = (n, Negative ns) :: k in
simplify_head_pat
~add_column n ns (simplify_first_amb_col rem)
| Positive { row = p::ps; varsets; }::rem ->
let add_column p ps k = (p, Positive ps) :: k in
simplify_head_amb_pat
Ident.Set.empty varsets
~add_column p ps (simplify_first_amb_col rem)
(* Compute stable bindings *)
type stable_vars =
| All
| Vars of Ident.Set.t
let stable_inter sv1 sv2 = match sv1, sv2 with
| All, sv | sv, All -> sv
| Vars s1, Vars s2 -> Vars (Ident.Set.inter s1 s2)
let reduce f = function
| [] -> invalid_arg "reduce"
| x::xs -> List.fold_left f x xs
let rec matrix_stable_vars m = match m with
| [] -> All
| ((Positive {row = []; _} | Negative []) :: _) as empty_rows ->
let exception Negative_empty_row in
(* if at least one empty row is negative, the matrix matches no value *)
let get_varsets = function
| Negative n ->
(* All rows have the same number of columns;
if the first row is empty, they all are. *)
assert (n = []);
raise Negative_empty_row
| Positive p ->
assert (p.row = []);
p.varsets in
begin match List.map get_varsets empty_rows with
| exception Negative_empty_row -> All
| rows_varsets ->
let stables_in_varsets =
reduce (List.map2 Ident.Set.inter) rows_varsets in
(* The stable variables are those stable at any position *)
Vars
(List.fold_left Ident.Set.union Ident.Set.empty stables_in_varsets)
end
| m ->
let is_negative = function
| Negative _ -> true
| Positive _ -> false in
if List.for_all is_negative m then
(* optimization: quit early if there are no positive rows.
This may happen often when the initial matrix has many
negative cases and few positive cases (a small guarded
clause after a long list of clauses) *)
All
else begin
let m = simplify_first_amb_col m in
if not (all_coherent (first_column m)) then
All
else begin
(* If the column is ill-typed but deemed coherent, we might
spuriously warn about some variables being unstable.
As sad as that might be, the warning can be silenced by
splitting the or-pattern... *)
let submatrices =
let extend_row columns = function
| Negative r -> Negative (columns @ r)
| Positive r -> Positive { r with row = columns @ r.row } in
let q0 = discr_pat Patterns.Simple.omega m in
let { default; constrs } =
build_specialized_submatrices ~extend_row q0 m in
let non_default = List.map snd constrs in
if full_match false constrs
then non_default
else default :: non_default in
(* A stable variable must be stable in each submatrix. *)
let submat_stable = List.map matrix_stable_vars submatrices in
List.fold_left stable_inter All submat_stable
end
end
let pattern_stable_vars ns p =
matrix_stable_vars
(List.fold_left (fun m n -> Negative n :: m)
[Positive {varsets = []; row = [p]}] ns)
(* All identifier paths that appear in an expression that occurs
as a clause right hand side or guard.
*)
let all_rhs_idents exp =
let ids = ref Ident.Set.empty in
let open Tast_iterator in
let expr_iter iter exp =
match exp.exp_desc with
| Texp_ident (path, _lid, _descr) ->
List.iter (fun id -> ids := Ident.Set.add id !ids) (Path.heads path)
(* Use default iterator methods for rest of match.*)
| _ -> Tast_iterator.default_iterator.expr iter exp
in
let iterator = {Tast_iterator.default_iterator with expr = expr_iter} in
iterator.expr iterator exp;
!ids
let check_ambiguous_bindings =
let open Warnings in
let warn0 = Ambiguous_var_in_pattern_guard [] in
fun cases ->
if is_active warn0 then
let check_case ns case = match case with
| { c_lhs = p; c_guard=None ; _} -> [p]::ns
| { c_lhs = p; c_guard=Some g; _} ->
let all =
Ident.Set.inter (pattern_vars p) (all_rhs_idents g) in
if not (Ident.Set.is_empty all) then begin
match pattern_stable_vars ns p with
| All -> ()
| Vars stable ->
let ambiguous = Ident.Set.diff all stable in
if not (Ident.Set.is_empty ambiguous) then begin
let pps =
Ident.Set.elements ambiguous |> List.map Ident.name in
let warn = Ambiguous_var_in_pattern_guard pps in
Location.prerr_warning p.pat_loc warn
end
end;
ns
in
ignore (List.fold_left check_case [] cases)
|