diff --git a/Cargo.lock b/Cargo.lock index d07f3f11..5e8d82d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -19,63 +19,64 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -100,18 +101,24 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" @@ -154,18 +161,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstyle", "clap_lex", @@ -173,9 +180,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" @@ -201,9 +208,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -272,9 +279,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -284,15 +291,15 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", @@ -319,19 +326,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -341,9 +348,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -352,9 +359,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -362,15 +369,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "humantime" @@ -380,9 +387,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -390,15 +397,21 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -416,24 +429,25 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" @@ -443,21 +457,21 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -467,15 +481,15 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -483,15 +497,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "ordered-float" @@ -524,9 +538,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -537,24 +551,27 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "priority-queue" @@ -569,9 +586,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -609,7 +626,8 @@ dependencies = [ "ron", "rustc-hash 2.1.0", "serde", - "thiserror 2.0.6", + "smallvec", + "thiserror 2.0.7", "varisat", "version-ranges", ] @@ -622,9 +640,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -670,9 +688,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -690,9 +708,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -702,9 +720,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -713,9 +731,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ron" @@ -744,15 +762,15 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -769,9 +787,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -784,31 +802,31 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "itoa 1.0.10", + "itoa 1.0.14", "memchr", "ryu", "serde", @@ -836,9 +854,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -859,54 +877,55 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.68", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.7", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -927,21 +946,21 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "varisat" @@ -957,7 +976,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-checker", "varisat-dimacs", "varisat-formula", @@ -977,7 +996,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-dimacs", "varisat-formula", "varisat-internal-proof", @@ -991,7 +1010,7 @@ checksum = "3d1dee4e21be1f04c0a939f7ae710cced47233a578de08a1b3c7d50848402636" dependencies = [ "anyhow", "itoa 0.4.8", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-formula", ] @@ -1067,34 +1086,34 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1102,64 +1121,42 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -1175,7 +1172,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -1195,17 +1201,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1216,9 +1223,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1228,9 +1235,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -1240,9 +1247,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -1252,9 +1265,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -1264,9 +1277,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -1276,9 +1289,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -1288,6 +1301,27 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] diff --git a/Cargo.toml b/Cargo.toml index 49362ab5..4bca2e59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,6 @@ keywords = ["dependency", "pubgrub", "semver", "solver", "version"] categories = ["algorithms"] include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**", "benches/**"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] indexmap = "2.6.0" # for debug logs in tests @@ -29,6 +27,7 @@ log = "0.4.22" priority-queue = "2.1.1" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } +smallvec = { version = "1.13.2", features = ["union"] } thiserror = "2.0" version-ranges = { version = "0.1.0", path = "version-ranges" } diff --git a/README.md b/README.md index 9c9e547d..b08ff9a2 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,6 @@ So, because root depends on both menu >=1.0.0 and intl >=5.0.0, ``` This pubgrub crate provides a Rust implementation of PubGrub. -It is generic and works for any type of dependency system -as long as packages (P) and versions (V) implement -the provided `Package` and `Version` traits. - ## Using the pubgrub crate diff --git a/benches/backtracking.rs b/benches/backtracking.rs index 689e437d..8d84ce73 100644 --- a/benches/backtracking.rs +++ b/benches/backtracking.rs @@ -26,7 +26,7 @@ fn backtracking_singletons(c: &mut Criterion, package_count: u32, version_count: c.bench_function("backtracking_singletons", |b| { b.iter(|| { - let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + let _ = dependency_provider.resolve(0u32, 0u32); }) }); } @@ -59,7 +59,7 @@ fn backtracking_disjoint_versions(c: &mut Criterion, package_count: u32, version c.bench_function("backtracking_disjoint_versions", |b| { b.iter(|| { - let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + let _ = dependency_provider.resolve(0u32, 0u32); }) }); } @@ -83,7 +83,7 @@ fn backtracking_ranges(c: &mut Criterion, package_count: u32, version_count: u32 c.bench_function("backtracking_ranges", |b| { b.iter(|| { - let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + let _ = dependency_provider.resolve(0u32, 0u32); }) }); } diff --git a/benches/large_case.rs b/benches/large_case.rs index 899d8a0d..97e107d3 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -1,24 +1,38 @@ // SPDX-License-Identifier: MPL-2.0 - +use std::fmt::{Debug, Display}; +use std::hash::Hash; use std::time::Duration; use criterion::*; +use pubgrub::{Map, OfflineDependencyProvider, Range, VersionRanges}; use serde::de::Deserialize; -use pubgrub::{resolve, OfflineDependencyProvider, Package, Range, SemanticVersion, VersionSet}; - -fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>( +fn bench< + 'a, + P: Debug + Display + Clone + Eq + Hash + Deserialize<'a>, + R: VersionRanges + Deserialize<'a>, +>( b: &mut Bencher, case: &'a str, ) where - ::V: Deserialize<'a>, + R::V: Deserialize<'a>, { - let dependency_provider: OfflineDependencyProvider = ron::de::from_str(case).unwrap(); + let mut dependency_provider: OfflineDependencyProvider = ron::de::from_str(case).unwrap(); + + let dependencies = dependency_provider + .packages() + .map(|p| { + ( + p.clone(), + dependency_provider.versions(p).unwrap().cloned().collect(), + ) + }) + .collect::>>(); b.iter(|| { - for p in dependency_provider.packages() { - for n in dependency_provider.versions(p).unwrap() { - let _ = resolve(&dependency_provider, p.clone(), n.clone()); + for (p, versions) in &dependencies { + for v in versions { + let _ = dependency_provider.resolve(p.clone(), v.clone()); } } }); @@ -32,14 +46,10 @@ fn bench_nested(c: &mut Criterion) { let case = case.unwrap().path(); let name = case.file_name().unwrap().to_string_lossy(); let data = std::fs::read_to_string(&case).unwrap(); - if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { + if name.ends_with("u16_NumberVersion.ron") { group.bench_function(name, |b| { bench::>(b, &data); }); - } else if name.ends_with("str_SemanticVersion.ron") { - group.bench_function(name, |b| { - bench::<&str, Range>(b, &data); - }); } } diff --git a/benches/sudoku.rs b/benches/sudoku.rs index 38081e62..de0953eb 100644 --- a/benches/sudoku.rs +++ b/benches/sudoku.rs @@ -3,12 +3,12 @@ //! Uses `Arc` for being closer to real versions. // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{resolve, OfflineDependencyProvider, Range}; use std::fmt; use std::sync::Arc; -use version_ranges::Ranges; use criterion::*; +use pubgrub::{OfflineDependencyProvider, Range}; +use version_ranges::Ranges; /// The size of a box in the board. const BOARD_BASE: usize = 3; @@ -122,7 +122,7 @@ fn solve(c: &mut Criterion, board: Vec<(SudokuPackage, Ranges>)>, cas dependency_provider.add_dependencies(SudokuPackage::Root, Arc::new(1usize), board); c.bench_function(case, |b| { b.iter(|| { - let _ = resolve(&dependency_provider, SudokuPackage::Root, Arc::new(1usize)); + let _ = dependency_provider.resolve(SudokuPackage::Root, Arc::new(1usize)); }) }); } diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index b4ef0d8a..fda327fe 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, + DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; @@ -10,15 +10,16 @@ type SemVS = Ranges; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); + #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 - dependency_provider.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), [("foo", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0)))], ); #[rustfmt::skip] // foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0 - dependency_provider.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 0, 0), [ ("a", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), @@ -27,7 +28,7 @@ fn main() { ); #[rustfmt::skip] // foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0 - dependency_provider.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 1, 0), [ ("x", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), @@ -36,7 +37,7 @@ fn main() { ); #[rustfmt::skip] // a 1.0.0 depends on b ^2.0.0 - dependency_provider.add_dependencies( + dependency_provider.add_dependencies( "a", (1, 0, 0), [("b", Ranges::from_range_bounds((2, 0, 0)..(3, 0, 0)))], ); @@ -45,7 +46,7 @@ fn main() { dependency_provider.add_dependencies("b", (2, 0, 0), []); #[rustfmt::skip] // x 1.0.0 depends on y ^2.0.0. - dependency_provider.add_dependencies( + dependency_provider.add_dependencies( "x", (1, 0, 0), [("y", Ranges::from_range_bounds((2, 0, 0)..(3, 0, 0)))], ); @@ -54,11 +55,14 @@ fn main() { dependency_provider.add_dependencies("y", (2, 0, 0), []); // Run the algorithm. - match resolve(&dependency_provider, "root", (1, 0, 0)) { + match dependency_provider.resolve("root", (1, 0, 0)) { Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + Err(PubGrubError::NoSolution(mut error)) => { + error.derivation_tree.collapse_no_versions(); + eprintln!( + "{}", + DefaultStringReporter::report(&error, &dependency_provider) + ); std::process::exit(1); } Err(err) => panic!("{:?}", err), diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index f6798a29..4acbb7b4 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -1,72 +1,158 @@ // SPDX-License-Identifier: MPL-2.0 use std::cell::RefCell; +use std::fmt::{Debug, Display}; +use std::hash::Hash; -use pubgrub::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, Ranges}; +use pubgrub::{ + helpers::PackageVersionWrapper, resolve, Dependencies, DependencyConstraints, + DependencyProvider, Map, OfflineDependencyProvider, PackageArena, PackageId, PubGrubError, + Ranges, SelectedDependencies, VersionIndex, VersionRanges, VersionSet, +}; type NumVS = Ranges; +trait RemoteProvider: DependencyProvider

> { + type Pkg: Debug + Display + Clone + Eq + Hash; + type R: VersionRanges; + + fn resolve_parameters( + &self, + p: Self::Pkg, + v: impl Into<::V>, + ) -> Option<(PackageVersionWrapper, VersionIndex)>; +} + +impl RemoteProvider + for OfflineDependencyProvider +{ + type Pkg = P; + type R = R; + + fn resolve_parameters( + &self, + p: P, + v: impl Into<::V>, + ) -> Option<(PackageVersionWrapper

, VersionIndex)> { + self.resolve_parameters(p, v) + } +} + // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. -struct CachingDependencyProvider { +struct CachingDependencyProvider, R: VersionRanges> +where + DP::P: Debug + Display + Clone + Eq + Hash, +{ remote_dependencies: DP, - cached_dependencies: RefCell>, + cached_dependencies: RefCell>>, } -impl CachingDependencyProvider { - pub fn new(remote_dependencies_provider: DP) -> Self { +impl, R: VersionRanges> CachingDependencyProvider +where + DP::P: Debug + Display + Clone + Eq + Hash, +{ + fn new(remote_dependencies_provider: DP) -> Self { CachingDependencyProvider { remote_dependencies: remote_dependencies_provider, - cached_dependencies: RefCell::new(OfflineDependencyProvider::new()), + cached_dependencies: Default::default(), } } + + fn resolve( + &mut self, + p: ::Pkg, + v: impl Into, + ) -> Result, PubGrubError> { + let Some((p, v)) = self.remote_dependencies.resolve_parameters(p, v) else { + return Err(PubGrubError::NoRoot); + }; + resolve(self, p, v) + } } -impl> DependencyProvider for CachingDependencyProvider { - // Caches dependencies if they were already queried +impl, R: VersionRanges> DependencyProvider + for CachingDependencyProvider +where + DP::P: Debug + Display + Clone + Eq + Hash, + R::V: Clone, +{ + // Cache dependencies if they were already queried fn get_dependencies( - &self, - package: &DP::P, - version: &DP::V, - ) -> Result, DP::Err> { + &mut self, + package_id: PackageId, + version_index: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); - match cache.get_dependencies(package, version) { - Ok(Dependencies::Unavailable(_)) => { - let dependencies = self.remote_dependencies.get_dependencies(package, version); - match dependencies { - Ok(Dependencies::Available(dependencies)) => { - cache.add_dependencies( - package.clone(), - version.clone(), - dependencies.clone(), - ); - Ok(Dependencies::Available(dependencies)) - } - Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)), - error @ Err(_) => error, - } + if let Some(deps) = cache + .get(&package_id) + .and_then(|vmap| vmap.get(&version_index)) + { + return Ok(Dependencies::Available(deps.clone())); + } + + match self + .remote_dependencies + .get_dependencies(package_id, version_index, package_store) + { + Ok(Dependencies::Available(deps)) => { + cache + .entry(package_id) + .or_default() + .insert(version_index, deps.clone()); + Ok(Dependencies::Available(deps)) } - Ok(dependencies) => Ok(dependencies), - Err(_) => unreachable!(), + + Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)), + error @ Err(_) => error, } } - fn choose_version(&self, package: &DP::P, ranges: &DP::VS) -> Result, DP::Err> { - self.remote_dependencies.choose_version(package, ranges) + fn choose_version( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Result, DP::Err> { + self.remote_dependencies + .choose_version(package_id, set, package_store) } type Priority = DP::Priority; - fn prioritize(&self, package: &DP::P, ranges: &DP::VS) -> Self::Priority { - self.remote_dependencies.prioritize(package, ranges) + fn prioritize( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Self::Priority { + self.remote_dependencies + .prioritize(package_id, set, package_store) } type Err = DP::Err; type P = DP::P; - type V = DP::V; - type VS = DP::VS; type M = DP::M; + + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a { + self.remote_dependencies + .package_version_display(package, version_index) + } + + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a { + self.remote_dependencies + .package_version_set_display(package, version_set) + } } fn main() { @@ -76,9 +162,9 @@ fn main() { // Add dependencies as needed. Here only root package is added. remote_dependencies_provider.add_dependencies("root", 1u32, Vec::new()); - let caching_dependencies_provider = + let mut caching_dependencies_provider = CachingDependencyProvider::new(remote_dependencies_provider); - let solution = resolve(&caching_dependencies_provider, "root", 1u32); + let solution = caching_dependencies_provider.resolve("root", 1u32); println!("Solution: {:?}", solution); } diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index fdbd5a2f..b8c8e88c 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{resolve, OfflineDependencyProvider, Ranges}; +use pubgrub::{OfflineDependencyProvider, Ranges}; type NumVS = Ranges; @@ -19,6 +19,6 @@ fn main() { dependency_provider.add_dependencies("icons", 1u32, []); // Run the algorithm. - let solution = resolve(&dependency_provider, "root", 1u32); - println!("Solution: {:?}", solution); + let solution = dependency_provider.resolve("root", 1u32).unwrap(); + println!("Solution: {solution:?}"); } diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 608f8b51..b5ee85f7 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, + DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; @@ -71,11 +71,11 @@ fn main() { dependency_provider.add_dependencies("intl", (5, 0, 0), []); // Run the algorithm. - match resolve(&dependency_provider, "root", (1, 0, 0)) { - Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + match dependency_provider.resolve("root", (1, 0, 0)) { + Ok(sol) => println!("Solution: {sol:?}"), + Err(PubGrubError::NoSolution(mut error)) => { + error.derivation_tree.collapse_no_versions(); + eprintln!("{}", DefaultStringReporter::report(&error, &dependency_provider)); } Err(err) => panic!("{:?}", err), }; diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index d043b9fd..74c6e4de 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, + DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; @@ -62,11 +62,11 @@ fn main() { dependency_provider.add_dependencies("icons", (2, 0, 0), []); // Run the algorithm. - match resolve(&dependency_provider, "root", (1, 0, 0)) { - Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + match dependency_provider.resolve("root", (1, 0, 0)) { + Ok(sol) => println!("Solution: {sol:?}"), + Err(PubGrubError::NoSolution(mut error)) => { + error.derivation_tree.collapse_no_versions(); + eprintln!("{}", DefaultStringReporter::report(&error, &dependency_provider)); } Err(err) => panic!("{:?}", err), }; diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index 490a6d87..0144ef8b 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, + DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; @@ -36,11 +36,14 @@ fn main() { dependency_provider.add_dependencies("baz", (3, 0, 0), []); // Run the algorithm. - match resolve(&dependency_provider, "root", (1, 0, 0)) { - Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + match dependency_provider.resolve("root", (1, 0, 0)) { + Ok(sol) => println!("Solution: {sol:?}"), + Err(PubGrubError::NoSolution(mut error)) => { + error.derivation_tree.collapse_no_versions(); + eprintln!( + "{}", + DefaultStringReporter::report(&error, &dependency_provider) + ); std::process::exit(1); } Err(err) => panic!("{:?}", err), diff --git a/examples/many_versions.rs b/examples/many_versions.rs new file mode 100644 index 00000000..ee812184 --- /dev/null +++ b/examples/many_versions.rs @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::cmp::Reverse; +use std::convert::Infallible; +use std::fmt::Display; +use std::num::NonZeroU64; + +use pubgrub::{ + helpers::PackageVersionWrapper, resolve, DefaultStringReporter, Dependencies, + DependencyProvider, Map, PackageArena, PackageId, PubGrubError, Reporter, VersionIndex, + VersionSet, +}; + +struct Provider { + version_counts: Map<&'static str, u64>, +} + +impl DependencyProvider for Provider { + type P = PackageVersionWrapper<&'static str>; + type M = &'static str; + type Priority = Reverse; + type Err = Infallible; + + fn prioritize( + &mut self, + _: PackageId, + set: VersionSet, + _: &PackageArena, + ) -> Self::Priority { + Reverse(set.count() as u64) + } + + fn choose_version( + &mut self, + _: PackageId, + set: VersionSet, + _: &PackageArena, + ) -> Result, Self::Err> { + Ok(set.last()) + } + + fn get_dependencies( + &mut self, + package: PackageId, + version_index: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, Self::Err> { + let mut dep_map = Map::default(); + + let add_dep = |store: &mut PackageArena, dep_map: &mut Map<_, _>, (d, vs)| { + dep_map.insert(store.insert(d), vs); + }; + + let add_range_dep = + |store: &mut PackageArena, dep_map: &mut Map<_, _>, (dn, r, vc)| { + add_dep(store, dep_map, PackageVersionWrapper::new_dep(dn, r, vc)); + }; + + let add_singleton_dep = + |store: &mut PackageArena, dep_map: &mut Map<_, _>, (dn, v, vc)| { + let (d, vs) = PackageVersionWrapper::new_singleton_dep(dn, v, vc); + add_dep(store, dep_map, (d, vs)); + }; + + let wrapper = package_store.pkg(package).unwrap(); + let inner = wrapper.inner(version_index).map(|(&p, v)| (p, v)); + + if let Some((d, vs)) = wrapper.dependency(version_index) { + add_dep(package_store, &mut dep_map, (d, vs)); + } + + if let Some((name, true_version_index)) = inner { + match (name, true_version_index) { + ("root", 0) => { + { + let dn = "dep1"; + match self.version_counts.get(dn) { + Some(&vc) => { + add_singleton_dep(package_store, &mut dep_map, (dn, 1, vc)) + } + None => return Ok(Dependencies::Unavailable("unavailable")), + } + } + { + let dn = "dep2"; + match self.version_counts.get(dn) { + Some(&vc) => { + add_range_dep(package_store, &mut dep_map, (dn, 0..64, vc)) + } + None => return Ok(Dependencies::Unavailable("unavailable")), + } + } + } + ("dep1", 1) => { + let dn = "many"; + match self.version_counts.get(dn) { + Some(&vc) => add_range_dep(package_store, &mut dep_map, (dn, 0..10000, vc)), + None => return Ok(Dependencies::Unavailable("unavailable")), + } + } + ("dep2", 1) | ("many", _) => (), + _ => return Ok(Dependencies::Unavailable("unavailable")), + } + }; + + Ok(Dependencies::Available(dep_map)) + } + + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a { + match package.inner(version_index) { + Some((&pkg, true_version_index)) => format!("{pkg} @ {true_version_index}"), + None => format!("{package} @ {}", version_index.get()), + } + } + + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a { + let version_indices = version_set + .iter() + .map(|version_index| match package.inner(version_index) { + Some((_, version_index)) => version_index, + None => version_index.get() as u64, + }) + .collect::>(); + + match package.inner_pkg() { + Some(p) => format!("{p} @ {version_indices:?}"), + _ => format!("{package} @ {version_indices:?}"), + } + } +} + +fn main() { + let (root_pkg, root_version_index) = + PackageVersionWrapper::new_pkg("root", 0, NonZeroU64::new(1).unwrap()); + + let mut provider = Provider { + version_counts: Map::from_iter([("root", 1), ("dep1", 63), ("dep2", 64), ("many", 10000)]), + }; + + match resolve(&mut provider, root_pkg, root_version_index) { + Ok(sol) => { + for (p, &v) in &sol { + let pv = provider.package_version_display(p, v); + println!("{pv}"); + } + } + Err(PubGrubError::NoSolution(mut error)) => { + error.derivation_tree.collapse_no_versions(); + eprintln!("{}", DefaultStringReporter::report(&error, &provider)); + std::process::exit(1); + } + Err(err) => panic!("{:?}", err), + } +} diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 3096ac70..dc6c1c0d 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -1,59 +1,75 @@ // SPDX-License-Identifier: MPL-2.0 -use std::fmt::{self, Display}; +use std::fmt::{self, Debug, Display}; +use std::hash::Hash; use pubgrub::{ - resolve, DefaultStringReporter, Derived, External, Map, OfflineDependencyProvider, - PubGrubError, Ranges, ReportFormatter, Reporter, SemanticVersion, Term, + helpers::PackageVersionWrapper, DefaultStringReporter, DependencyProvider, Derived, External, + Map, OfflineDependencyProvider, PackageArena, PackageId, PubGrubError, Ranges, ReportFormatter, + Reporter, SemanticVersion, Term, VersionSet, }; #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Package { +pub enum CustomPackage { Root, Package(String), } -impl Display for Package { +impl Display for CustomPackage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Package::Root => write!(f, "root"), - Package::Package(name) => write!(f, "{}", name), + CustomPackage::Root => write!(f, "root"), + CustomPackage::Package(name) => write!(f, "{name}"), } } } +type Store = PackageArena>; +type Dp = OfflineDependencyProvider>; + #[derive(Debug, Default)] struct CustomReportFormatter; -impl ReportFormatter, String> for CustomReportFormatter { +impl ReportFormatter for CustomReportFormatter { type Output = String; - fn format_terms(&self, terms: &Map>>) -> String { - let terms_vec: Vec<_> = terms.iter().collect(); - match terms_vec.as_slice() { + fn format_terms(&self, terms: &Map, package_store: &Store, dp: &Dp) -> String { + let terms_vec: Vec<_> = terms + .iter() + .map(|(&pid, &v)| (pid, package_store.pkg(pid).unwrap(), v)) + .collect(); + match *terms_vec.as_slice() { [] => "version solving failed".into(), - [(package @ Package::Root, Term::Positive(_))] => { - format!("{package} is forbidden") - } - [(package @ Package::Root, Term::Negative(_))] => { - format!("{package} is mandatory") - } - [(package @ Package::Package(_), Term::Positive(ranges))] => { - format!("{package} {ranges} is forbidden") - } - [(package @ Package::Package(_), Term::Negative(ranges))] => { - format!("{package} {ranges} is mandatory") - } - [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { - External::<_, _, String>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()) + [(pid, package, t)] => match package.inner_pkg() { + Some(&CustomPackage::Root) if t.is_positive() => format!("{package} is forbidden"), + Some(&CustomPackage::Root) => format!("{package} is mandatory"), + _ if t.is_positive() => format!( + "{} is forbidden", + dp.package_version_set_display( + package_store.pkg(pid).unwrap(), + t.version_set() + ), + ), + _ => format!( + "{} is mandatory", + dp.package_version_set_display( + package_store.pkg(pid).unwrap(), + t.version_set() + ), + ), + }, + [(pid1, _, t1), (pid2, _, t2)] if t1.is_positive() && t2.is_negative() => { + External::FromDependencyOf(pid1, t1.version_set(), pid2, t2.version_set()) + .display(package_store, dp) .to_string() } - [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { - External::<_, _, String>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()) + [(pid1, _, t1), (pid2, _, t2)] if t1.is_negative() && t2.is_positive() => { + External::FromDependencyOf(pid2, t2.version_set(), pid1, t1.version_set()) + .display(package_store, dp) .to_string() } - slice => { - let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect(); + ref slice => { + let str_terms: Vec<_> = slice.iter().map(|(_, p, t)| format!("{p} {t}")).collect(); str_terms.join(", ") + " are incompatible" } } @@ -61,43 +77,72 @@ impl ReportFormatter, String> for CustomReportF fn format_external( &self, - external: &External, String>, + external: &External<&'static str>, + package_store: &Store, + dp: &Dp, ) -> String { - match external { - External::NotRoot(package, version) => { - format!("we are solving dependencies of {package} {version}") + match *external { + External::NotRoot(package_id, version_index) => { + let pkg = package_store.pkg(package_id).unwrap(); + format!( + "we are solving dependencies of {}", + dp.package_version_display(pkg, version_index), + ) } - External::NoVersions(package, set) => { - if set == &Ranges::full() { - format!("there is no available version for {package}") + External::NoVersions(package_id, set) => { + let pkg = package_store.pkg(package_id).unwrap(); + if set == VersionSet::full() { + format!("there is no available version for {pkg}") } else { - format!("there is no version of {package} in {set}") + format!( + "there is no version of {}", + dp.package_version_set_display(pkg, set), + ) } } - External::Custom(package, set, reason) => { - if set == &Ranges::full() { - format!("dependencies of {package} are unavailable because {reason}") + External::Custom(package_id, set, ref reason) => { + let pkg = package_store.pkg(package_id).unwrap(); + if set == VersionSet::full() { + format!("dependencies of {pkg} are unavailable because {reason}") } else { - format!("dependencies of {package} at version {set} are unavailable because {reason}") + format!( + "dependencies of {} are unavailable because {reason}", + dp.package_version_set_display(pkg, set), + ) } } - External::FromDependencyOf(package, package_set, dependency, dependency_set) => { - if package_set == &Ranges::full() && dependency_set == &Ranges::full() { - format!("{package} depends on {dependency}") - } else if package_set == &Ranges::full() { - format!("{package} depends on {dependency} {dependency_set}") - } else if dependency_set == &Ranges::full() { - if matches!(package, Package::Root) { + External::FromDependencyOf(package_id, package_set, dep_id, dep_set) => { + let pkg = package_store.pkg(package_id).unwrap(); + let dep_pkg = package_store.pkg(dep_id).unwrap(); + if package_set == VersionSet::full() && dep_set == VersionSet::full() { + format!("{pkg} depends on {dep_pkg}") + } else if package_set == VersionSet::full() { + format!( + "{pkg} depends on {}", + dp.package_version_set_display(dep_pkg, dep_set), + ) + } else if dep_set == VersionSet::full() { + if pkg.inner_pkg() == Some(&CustomPackage::Root) { // Exclude the dummy version for root packages - format!("{package} depends on {dependency}") + format!("{pkg} depends on {dep_pkg}") } else { - format!("{package} {package_set} depends on {dependency}") + format!( + "{} depends on {dep_pkg}", + dp.package_version_set_display(pkg, package_set), + ) } - } else if matches!(package, Package::Root) { + } else if pkg.inner_pkg() == Some(&CustomPackage::Root) { // Exclude the dummy version for root packages - format!("{package} depends on {dependency} {dependency_set}") + format!( + "{pkg} depends on {}", + dp.package_version_set_display(dep_pkg, dep_set), + ) } else { - format!("{package} {package_set} depends on {dependency} {dependency_set}") + format!( + "{} depends on {}", + dp.package_version_set_display(pkg, package_set), + dp.package_version_set_display(dep_pkg, dep_set), + ) } } } @@ -106,16 +151,18 @@ impl ReportFormatter, String> for CustomReportF /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, - external1: &External, String>, - external2: &External, String>, - current_terms: &Map>>, + external1: &External<&'static str>, + external2: &External<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} and {}, {}.", - self.format_external(external1), - self.format_external(external2), - self.format_terms(current_terms) + self.format_external(external1, package_store, dp), + self.format_external(external2, package_store, dp), + self.format_terms(current_terms, package_store, dp) ) } @@ -123,19 +170,21 @@ impl ReportFormatter, String> for CustomReportF fn explain_both_ref( &self, ref_id1: usize, - derived1: &Derived, String>, + derived1: &Derived<&'static str>, ref_id2: usize, - derived2: &Derived, String>, - current_terms: &Map>>, + derived2: &Derived<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {} ({}), {}.", - self.format_terms(&derived1.terms), + self.format_terms(&derived1.terms, package_store, dp), ref_id1, - self.format_terms(&derived2.terms), + self.format_terms(&derived2.terms, package_store, dp), ref_id2, - self.format_terms(current_terms) + self.format_terms(current_terms, package_store, dp) ) } @@ -145,30 +194,34 @@ impl ReportFormatter, String> for CustomReportF fn explain_ref_and_external( &self, ref_id: usize, - derived: &Derived, String>, - external: &External, String>, - current_terms: &Map>>, + derived: &Derived<&'static str>, + external: &External<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {}, {}.", - self.format_terms(&derived.terms), + self.format_terms(&derived.terms, package_store, dp), ref_id, - self.format_external(external), - self.format_terms(current_terms) + self.format_external(external, package_store, dp), + self.format_terms(current_terms, package_store, dp) ) } /// Add an external cause to the chain of explanations. fn and_explain_external( &self, - external: &External, String>, - current_terms: &Map>>, + external: &External<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { format!( "And because {}, {}.", - self.format_external(external), - self.format_terms(current_terms) + self.format_external(external, package_store, dp), + self.format_terms(current_terms, package_store, dp) ) } @@ -176,55 +229,63 @@ impl ReportFormatter, String> for CustomReportF fn and_explain_ref( &self, ref_id: usize, - derived: &Derived, String>, - current_terms: &Map>>, + derived: &Derived<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { format!( "And because {} ({}), {}.", - self.format_terms(&derived.terms), + self.format_terms(&derived.terms, package_store, dp), ref_id, - self.format_terms(current_terms) + self.format_terms(current_terms, package_store, dp) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, - prior_external: &External, String>, - external: &External, String>, - current_terms: &Map>>, + prior_external: &External<&'static str>, + external: &External<&'static str>, + current_terms: &Map, + package_store: &Store, + dp: &Dp, ) -> String { format!( "And because {} and {}, {}.", - self.format_external(prior_external), - self.format_external(external), - self.format_terms(current_terms) + self.format_external(prior_external, package_store, dp), + self.format_external(external, package_store, dp), + self.format_terms(current_terms, package_store, dp) ) } } fn main() { let mut dependency_provider = - OfflineDependencyProvider::>::new(); + OfflineDependencyProvider::>::new(); + // Define the root package with a dependency on a package we do not provide dependency_provider.add_dependencies( - Package::Root, + CustomPackage::Root, (0, 0, 0), vec![( - Package::Package("foo".to_string()), + CustomPackage::Package("foo".to_string()), Ranges::singleton((1, 0, 0)), )], ); // Run the algorithm - match resolve(&dependency_provider, Package::Root, (0, 0, 0)) { + match dependency_provider.resolve(CustomPackage::Root, (0, 0, 0)) { Ok(sol) => println!("{:?}", sol), - Err(PubGrubError::NoSolution(derivation_tree)) => { + Err(PubGrubError::NoSolution(error)) => { eprintln!("No solution.\n"); eprintln!("### Default report:"); eprintln!("```"); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + eprintln!( + "{}", + DefaultStringReporter::report(&error, &dependency_provider) + ); eprintln!("```\n"); eprintln!("### Report with custom formatter:"); @@ -232,8 +293,9 @@ fn main() { eprintln!( "{}", DefaultStringReporter::report_with_formatter( - &derivation_tree, - &CustomReportFormatter + &error, + &CustomReportFormatter, + &dependency_provider ) ); eprintln!("```"); diff --git a/src/error.rs b/src/error.rs index ce193766..b6b88612 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,30 +4,34 @@ use thiserror::Error; -use crate::{DependencyProvider, DerivationTree}; +use crate::{DependencyProvider, DerivationTree, PackageArena}; /// There is no solution for this set of dependencies. -pub type NoSolutionError = DerivationTree< - ::P, - ::VS, - ::M, ->; +#[derive(Debug, Clone)] +pub struct NoSolutionError { + /// Package store + pub package_store: PackageArena, + /// Derivate tree + pub derivation_tree: DerivationTree, +} /// Errors that may occur while solving dependencies. #[derive(Error)] pub enum PubGrubError { + /// Root package name doesn't exist. + #[error("Root package name doesn't exist")] + NoRoot, + /// There is no solution for this set of dependencies. #[error("No solution")] - NoSolution(NoSolutionError), + NoSolution(Box>), /// Error arising when the implementer of [DependencyProvider] returned an error in the method /// [get_dependencies](DependencyProvider::get_dependencies). - #[error("Retrieving dependencies of {package} {version} failed")] + #[error("Retrieving dependencies of {package_version} failed")] ErrorRetrievingDependencies { - /// Package whose dependencies we want. - package: DP::P, - /// Version of the package for which we want the dependencies. - version: DP::V, + /// Represenatation of package and version whose dependencies we want. + package_version: String, /// Error raised by the implementer of /// [DependencyProvider]. source: DP::Err, @@ -50,7 +54,7 @@ pub enum PubGrubError { impl From> for PubGrubError { fn from(err: NoSolutionError) -> Self { - Self::NoSolution(err) + Self::NoSolution(err.into()) } } @@ -60,15 +64,18 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::NoSolution(err) => f.debug_tuple("NoSolution").field(&err).finish(), + Self::NoRoot => f.debug_struct("NoRoot").finish(), + Self::NoSolution(err) => f + .debug_struct("NoSolution") + .field("package_store", &err.package_store) + .field("derivation_tree", &err.derivation_tree) + .finish(), Self::ErrorRetrievingDependencies { - package, - version, + package_version, source, } => f .debug_struct("ErrorRetrievingDependencies") - .field("package", package) - .field("version", version) + .field("package_version", package_version) .field("source", source) .finish(), Self::ErrorChoosingPackageVersion(arg0) => f diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 00000000..9af419ff --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,355 @@ +//! Helpers structs. + +use std::fmt::{self, Display}; +use std::iter::repeat_n; +use std::num::NonZeroU64; +use std::rc::Rc; + +use crate::{VersionIndex, VersionSet}; + +/// Package allowing more than 63 versions. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Pkg

{ + pkg: P, + quotient: u64, + count: u64, +} + +impl

Pkg

{ + /// Get the inner package. + pub fn pkg(&self) -> &P { + &self.pkg + } +} + +impl Display for Pkg

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} (q={})", self.pkg, self.quotient) + } +} + +/// Virtual package ensuring package unicity. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct VirtualPkg

{ + pkg: P, + quotient: u64, + count: u64, +} + +impl

VirtualPkg

{ + /// Get the inner package. + pub fn pkg(&self) -> &P { + &self.pkg + } +} + +impl Display for VirtualPkg

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "VirtualPkg({}, q={}, c={})", + self.pkg, self.quotient, self.count + ) + } +} + +/// Virtual package dependency allowing more than 63 versions. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct VirtualDep

{ + pkg: P, + version_indices: Rc<[VersionSet]>, + offset: u64, + quotient: u64, +} + +impl

VirtualDep

{ + /// Get the inner package. + pub fn pkg(&self) -> &P { + &self.pkg + } +} + +impl Display for VirtualDep

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let vc = self + .version_indices + .iter() + .map(|vs| vs.count()) + .sum::(); + + write!( + f, + "VirtualDep({}, vc={vc}, o={}, q={})", + self.pkg, self.offset, self.quotient + ) + } +} + +/// Package wrapper used to allow more than 63 versions per package. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum PackageVersionWrapper { + /// Package allowing more than 63 versions + Pkg(Pkg

), + /// Virtual package ensuring package unicity + VirtualPkg(VirtualPkg

), + /// Virtual package dependency allowing more than 63 versions + VirtualDep(VirtualDep

), +} + +impl Display for PackageVersionWrapper

{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Pkg(p) => p.fmt(f), + Self::VirtualPkg(vp) => vp.fmt(f), + Self::VirtualDep(vd) => vd.fmt(f), + } + } +} + +impl PackageVersionWrapper

{ + /// Create a new package. + pub fn new_pkg( + pkg: P, + true_version_index: u64, + version_count: NonZeroU64, + ) -> (Self, VersionIndex) { + ( + Self::Pkg(Pkg { + pkg, + quotient: true_version_index / VersionIndex::MAX, + count: (version_count.get() - 1) / VersionIndex::MAX, + }), + VersionIndex::new((true_version_index % VersionIndex::MAX) as u8).unwrap(), + ) + } + + /// Create a new package dependency with no versions. + pub fn new_empty_dep(pkg: P) -> (Self, VersionSet) { + ( + Self::Pkg(Pkg { + pkg, + quotient: 0, + count: 0, + }), + VersionSet::empty(), + ) + } + + /// Create a new package dependency at the specified version. + pub fn new_singleton_dep( + pkg: P, + true_version_index: u64, + version_count: u64, + ) -> (Self, VersionSet) { + match NonZeroU64::new(version_count) { + Some(version_count) => { + assert!(true_version_index < version_count.get()); + let (this, v) = Self::new_pkg(pkg, true_version_index, version_count); + (this, VersionSet::singleton(v)) + } + None => Self::new_empty_dep(pkg), + } + } + + /// Create a new package dependency at the specified versions. + pub fn new_dep( + pkg: P, + true_version_indices: impl IntoIterator, + version_count: u64, + ) -> (Self, VersionSet) { + let Some(nz_version_count) = NonZeroU64::new(version_count) else { + return Self::new_empty_dep(pkg); + }; + if version_count <= VersionIndex::MAX { + let mut set = VersionSet::empty(); + for true_version_index in true_version_indices { + assert!(true_version_index < version_count); + let v = VersionIndex::new(true_version_index as u8).unwrap(); + set = set.union(VersionSet::singleton(v)); + } + return ( + Self::Pkg(Pkg { + pkg, + quotient: 0, + count: (version_count - 1) / VersionIndex::MAX, + }), + set, + ); + } + + let mut true_version_indices = true_version_indices.into_iter(); + + let Some(first) = true_version_indices.next() else { + return Self::new_empty_dep(pkg); + }; + assert!(first < version_count); + + let Some(second) = true_version_indices.next() else { + let (d, vs) = Self::new_pkg(pkg, first, nz_version_count); + return (d, VersionSet::singleton(vs)); + }; + assert!(second < version_count); + + let mut version_indices = Rc::from_iter(repeat_n( + VersionSet::empty(), + (1 + (version_count - 1) / VersionIndex::MAX) as usize, + )); + let versions_slice = Rc::make_mut(&mut version_indices); + + for true_version_index in [first, second].into_iter().chain(true_version_indices) { + assert!(true_version_index < version_count); + let index = (true_version_index / VersionIndex::MAX) as usize; + let v = VersionIndex::new((true_version_index % VersionIndex::MAX) as u8).unwrap(); + let set = versions_slice.get_mut(index).unwrap(); + *set = set.union(VersionSet::singleton(v)); + } + + let offset = 0; + let quotient = VersionIndex::MAX.pow(version_count.ilog(VersionIndex::MAX) - 1); + let version_set = Self::dep_version_set(&version_indices, offset, quotient); + + let this = Self::VirtualDep(VirtualDep { + pkg, + version_indices, + offset, + quotient, + }); + + (this, version_set) + } + + /// Clone and replace the package of this wrapper. + pub fn replace_pkg(&self, new_pkg: T) -> PackageVersionWrapper { + match *self { + Self::Pkg(Pkg { + pkg: _, + quotient, + count, + }) => PackageVersionWrapper::Pkg(Pkg { + pkg: new_pkg, + quotient, + count, + }), + Self::VirtualPkg(VirtualPkg { + pkg: _, + quotient, + count, + }) => PackageVersionWrapper::VirtualPkg(VirtualPkg { + pkg: new_pkg, + quotient, + count, + }), + Self::VirtualDep(VirtualDep { + pkg: _, + ref version_indices, + offset, + quotient, + }) => PackageVersionWrapper::VirtualDep(VirtualDep { + pkg: new_pkg, + version_indices: version_indices.clone(), + offset, + quotient, + }), + } + } + + /// Get the inner package if existing. + pub fn inner_pkg(&self) -> Option<&P> { + match self { + Self::Pkg(Pkg { pkg, .. }) => Some(pkg), + _ => None, + } + } + + /// Get the inner package if existing. + pub fn inner(&self, version_index: VersionIndex) -> Option<(&P, u64)> { + match self { + Self::Pkg(Pkg { pkg, quotient, .. }) => Some(( + pkg, + quotient * VersionIndex::MAX + version_index.get() as u64, + )), + _ => None, + } + } + + /// Get the inner package if existing. + pub fn into_inner(self, version_index: VersionIndex) -> Option<(P, u64)> { + match self { + Self::Pkg(Pkg { pkg, quotient, .. }) => Some(( + pkg, + quotient * VersionIndex::MAX + version_index.get() as u64, + )), + _ => None, + } + } + + /// Get the wrapper virtual dependency if existing. + pub fn dependency(&self, version_index: VersionIndex) -> Option<(Self, VersionSet)> { + match *self { + Self::Pkg(Pkg { + ref pkg, + quotient, + count, + }) + | Self::VirtualPkg(VirtualPkg { + ref pkg, + quotient, + count, + }) => { + if count == 0 { + None + } else { + Some(( + Self::VirtualPkg(VirtualPkg { + pkg: pkg.clone(), + quotient: quotient / VersionIndex::MAX, + count: count / VersionIndex::MAX, + }), + VersionSet::singleton( + VersionIndex::new((quotient % VersionIndex::MAX) as u8).unwrap(), + ), + )) + } + } + Self::VirtualDep(VirtualDep { + ref pkg, + ref version_indices, + offset, + quotient, + }) => { + let offset = offset + version_index.get() as u64 * quotient; + if quotient == 1 { + return Some(( + Self::Pkg(Pkg { + pkg: pkg.clone(), + quotient: offset, + count: (version_indices.len() - 1) as u64, + }), + version_indices[offset as usize], + )); + } + let quotient = quotient / VersionIndex::MAX; + let version_set = Self::dep_version_set(version_indices, offset, quotient); + + let this = Self::VirtualDep(VirtualDep { + pkg: pkg.clone(), + version_indices: version_indices.clone(), + offset, + quotient, + }); + + Some((this, version_set)) + } + } + } + + fn dep_version_set(sets: &[VersionSet], offset: u64, quotient: u64) -> VersionSet { + sets[offset as usize..] + .chunks(quotient as usize) + .take(VersionIndex::MAX as usize) + .enumerate() + .filter(|&(_, sets)| sets.iter().any(|&vs| vs != VersionSet::empty())) + .map(|(i, _)| VersionSet::singleton(VersionIndex::new(i as u8).unwrap())) + .fold(VersionSet::empty(), |acc, vs| acc.union(vs)) + } +} diff --git a/src/internal/arena.rs b/src/internal/arena.rs deleted file mode 100644 index e044bc37..00000000 --- a/src/internal/arena.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::marker::PhantomData; -use std::ops::{Index, Range}; - -type FnvIndexSet = indexmap::IndexSet; - -/// The index of a value allocated in an arena that holds `T`s. -/// -/// The Clone, Copy and other traits are defined manually because -/// deriving them adds some additional constraints on the `T` generic type -/// that we actually don't need since it is phantom. -/// -/// -pub(crate) struct Id { - raw: u32, - _ty: PhantomData T>, -} - -impl Clone for Id { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Id {} - -impl PartialEq for Id { - fn eq(&self, other: &Id) -> bool { - self.raw == other.raw - } -} - -impl Eq for Id {} - -impl Hash for Id { - fn hash(&self, state: &mut H) { - self.raw.hash(state) - } -} - -impl fmt::Debug for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut type_name = std::any::type_name::(); - if let Some(id) = type_name.rfind(':') { - type_name = &type_name[id + 1..] - } - write!(f, "Id::<{}>({})", type_name, self.raw) - } -} - -impl Id { - pub(crate) fn into_raw(self) -> usize { - self.raw as usize - } - fn from(n: u32) -> Self { - Self { - raw: n, - _ty: PhantomData, - } - } - pub(crate) fn range_to_iter(range: Range) -> impl Iterator { - let start = range.start.raw; - let end = range.end.raw; - (start..end).map(Self::from) - } -} - -/// Yet another index-based arena. -/// -/// An arena is a kind of simple grow-only allocator, backed by a `Vec` -/// where all items have the same lifetime, making it easier -/// to have references between those items. -/// They are all dropped at once when the arena is dropped. -#[derive(Clone, PartialEq, Eq)] -pub(crate) struct Arena { - data: Vec, -} - -impl fmt::Debug for Arena { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("Arena") - .field("len", &self.data.len()) - .field("data", &self.data) - .finish() - } -} - -impl Default for Arena { - fn default() -> Self { - Self::new() - } -} - -impl Arena { - pub(crate) fn new() -> Self { - Self { data: Vec::new() } - } - - pub(crate) fn alloc(&mut self, value: T) -> Id { - let raw = self.data.len(); - self.data.push(value); - Id::from(raw as u32) - } - - pub(crate) fn alloc_iter>(&mut self, values: I) -> Range> { - let start = Id::from(self.data.len() as u32); - values.for_each(|v| { - self.alloc(v); - }); - let end = Id::from(self.data.len() as u32); - Range { start, end } - } -} - -impl Index> for Arena { - type Output = T; - fn index(&self, id: Id) -> &T { - &self.data[id.raw as usize] - } -} - -impl Index>> for Arena { - type Output = [T]; - fn index(&self, id: Range>) -> &[T] { - &self.data[(id.start.raw as usize)..(id.end.raw as usize)] - } -} - -/// Yet another index-based arena. This one de-duplicates entries by hashing. -/// -/// An arena is a kind of simple grow-only allocator, backed by a `Vec` -/// where all items have the same lifetime, making it easier -/// to have references between those items. -/// In this case the `Vec` is inside a `IndexSet` allowing fast lookup by value not just index. -/// They are all dropped at once when the arena is dropped. -#[derive(Clone, PartialEq, Eq)] -pub struct HashArena { - data: FnvIndexSet, -} - -impl fmt::Debug for HashArena { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("Arena") - .field("len", &self.data.len()) - .field("data", &self.data) - .finish() - } -} - -impl HashArena { - pub fn new() -> Self { - HashArena { - data: FnvIndexSet::default(), - } - } - - pub fn alloc(&mut self, value: T) -> Id { - let (raw, _) = self.data.insert_full(value); - Id::from(raw as u32) - } -} - -impl Index> for HashArena { - type Output = T; - fn index(&self, id: Id) -> &T { - &self.data[id.raw as usize] - } -} diff --git a/src/internal/core.rs b/src/internal/core.rs index 6f70d04c..59eca6aa 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -3,78 +3,69 @@ //! Core model and functions //! to write a functional PubGrub algorithm. -use std::collections::HashSet as Set; use std::sync::Arc; -use crate::internal::{ - Arena, DecisionLevel, HashArena, Id, IncompDpId, Incompatibility, PartialSolution, Relation, - SatisfierSearch, SmallVec, +use smallvec::SmallVec; + +use crate::{ + internal::{ + DecisionLevel, IncompatArena, IncompatId, Incompatibility, PartialSolution, Relation, + SatisfierSearch, + }, + DependencyProvider, DerivationTree, Map, PackageArena, PackageId, Set, Term, VersionIndex, + VersionSet, }; -use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet}; /// Current state of the PubGrub algorithm. #[derive(Clone)] pub(crate) struct State { - pub root_package: Id, - root_version: DP::V, - - #[allow(clippy::type_complexity)] - incompatibilities: Map, Vec>>, + root_package_id: PackageId, + root_version_index: VersionIndex, - /// Store the ids of incompatibilities that are already contradicted. - /// For each one keep track of the decision level when it was found to be contradicted. - /// These will stay contradicted until we have backtracked beyond its associated decision level. - contradicted_incompatibilities: Map, DecisionLevel>, + incompatibilities: Vec>, /// All incompatibilities expressing dependencies, /// with common dependents merged. - #[allow(clippy::type_complexity)] - merged_dependencies: Map<(Id, Id), SmallVec>>, + merged_dependencies: Map<(PackageId, PackageId), SmallVec<[IncompatId; 4]>>, /// Partial solution. /// TODO: remove pub. pub(crate) partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. - pub(crate) incompatibility_store: Arena>, - - /// The store is the reference storage for all packages. - pub(crate) package_store: HashArena, + pub(crate) incompatibility_store: IncompatArena, /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but /// this way we can reuse the same allocation for better performance. - unit_propagation_buffer: SmallVec>, + unit_propagation_buffer: Vec, } impl State { /// Initialization of PubGrub state. - pub(crate) fn init(root_package: DP::P, root_version: DP::V) -> Self { - let mut incompatibility_store = Arena::new(); - let mut package_store = HashArena::new(); - let root_package = package_store.alloc(root_package); - let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( - root_package, - root_version.clone(), + pub(crate) fn init(root_package_id: PackageId, root_version_index: VersionIndex) -> Self { + let mut incompatibility_store = IncompatArena::new(); + let not_root_id = incompatibility_store.push(Incompatibility::not_root( + root_package_id, + root_version_index, )); - let mut incompatibilities = Map::default(); - incompatibilities.insert(root_package, vec![not_root_id]); + let root_package_idx = root_package_id.get() as usize; + let mut incompatibilities = vec![SmallVec::new(); root_package_idx + 1]; + incompatibilities[root_package_idx].push(not_root_id); Self { - root_package, - root_version, + root_package_id, + root_version_index, incompatibilities, - contradicted_incompatibilities: Map::default(), - partial_solution: PartialSolution::empty(), + partial_solution: PartialSolution::empty(root_package_id), incompatibility_store, - package_store, - unit_propagation_buffer: SmallVec::Empty, + unit_propagation_buffer: Vec::new(), merged_dependencies: Map::default(), } } /// Add an incompatibility to the state. - pub(crate) fn add_incompatibility(&mut self, incompat: Incompatibility) { - let id = self.incompatibility_store.alloc(incompat); + pub(crate) fn add_incompatibility(&mut self, incompat: Incompatibility) { + let id = self.incompatibility_store.push(incompat); self.merge_incompatibility(id); } @@ -82,23 +73,18 @@ impl State { #[cold] pub(crate) fn add_incompatibility_from_dependencies( &mut self, - package: Id, - version: DP::V, - deps: impl IntoIterator, - ) -> std::ops::Range> { + package_id: PackageId, + version_index: VersionIndex, + deps: impl IntoIterator, + ) -> std::ops::Range { // Create incompatibilities and allocate them in the store. - let new_incompats_id_range = - self.incompatibility_store - .alloc_iter(deps.into_iter().map(|(dep_p, dep_vs)| { - let dep_pid = self.package_store.alloc(dep_p); - Incompatibility::from_dependency( - package, - ::singleton(version.clone()), - (dep_pid, dep_vs), - ) - })); + let vs = VersionSet::singleton(version_index); + let new_incompats_id_range = self.incompatibility_store.extend( + deps.into_iter() + .map(|dep| Incompatibility::from_dependency(package_id, vs, dep)), + ); // Merge the newly created incompatibilities with the older ones. - for id in IncompDpId::::range_to_iter(new_incompats_id_range.clone()) { + for id in IncompatId::range_to_iter(new_incompats_id_range.clone()) { self.merge_incompatibility(id); } new_incompats_id_range @@ -109,30 +95,30 @@ impl State { #[cold] pub(crate) fn unit_propagation( &mut self, - package: Id, - ) -> Result<(), NoSolutionError> { + package_id: PackageId, + package_store: &PackageArena, + dependency_provider: &mut DP, + ) -> Result<(), DerivationTree> { self.unit_propagation_buffer.clear(); - self.unit_propagation_buffer.push(package); + self.unit_propagation_buffer.push(package_id); while let Some(current_package) = self.unit_propagation_buffer.pop() { // Iterate over incompatibilities in reverse order // to evaluate first the newest incompatibilities. let mut conflict_id = None; // We only care about incompatibilities if it contains the current package. - for &incompat_id in self.incompatibilities[¤t_package].iter().rev() { - if self - .contradicted_incompatibilities - .contains_key(&incompat_id) - { + let idx = current_package.get() as usize; + for &incompat_id in self.incompatibilities[idx].iter().rev() { + let current_incompat = &mut self.incompatibility_store[incompat_id]; + if self.partial_solution.is_contradicted(current_incompat) { continue; } - let current_incompat = &self.incompatibility_store[incompat_id]; match self.partial_solution.relation(current_incompat) { // If the partial solution satisfies the incompatibility // we must perform conflict resolution. Relation::Satisfied => { log::info!( "Start conflict resolution because incompat satisfied:\n {}", - current_incompat.display(&self.package_store) + current_incompat.display(package_store, dependency_provider) ); conflict_id = Some(incompat_id); break; @@ -149,37 +135,41 @@ impl State { self.partial_solution.add_derivation( package_almost, incompat_id, - &self.incompatibility_store, + current_incompat.get(package_almost).unwrap(), ); // With the partial solution updated, the incompatibility is now contradicted. - self.contradicted_incompatibilities - .insert(incompat_id, self.partial_solution.current_decision_level()); + self.partial_solution.contradict(current_incompat); } Relation::Contradicted(_) => { - self.contradicted_incompatibilities - .insert(incompat_id, self.partial_solution.current_decision_level()); + self.partial_solution.contradict(current_incompat); } _ => {} } } if let Some(incompat_id) = conflict_id { - let (package_almost, root_cause) = - self.conflict_resolution(incompat_id) - .map_err(|terminal_incompat_id| { - self.build_derivation_tree(terminal_incompat_id) - })?; + let (package_almost, root_cause) = self + .conflict_resolution(incompat_id, package_store, dependency_provider) + .map_err(|terminal_incompat_id| { + self.build_derivation_tree(terminal_incompat_id) + })?; + dependency_provider.register_conflict( + self.incompatibility_store[root_cause] + .iter() + .map(|(pid, _)| pid), + package_store, + ); self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package_almost); + let root_incompat = &mut self.incompatibility_store[root_cause]; // Add to the partial solution with incompat as cause. self.partial_solution.add_derivation( package_almost, root_cause, - &self.incompatibility_store, + root_incompat.get(package_almost).unwrap(), ); // After conflict resolution and the partial solution update, // the root cause incompatibility is now contradicted. - self.contradicted_incompatibilities - .insert(root_cause, self.partial_solution.current_decision_level()); + self.partial_solution.contradict(root_incompat); } } // If there are no more changed packages, unit propagation is done. @@ -188,47 +178,52 @@ impl State { /// Return the root cause or the terminal incompatibility. /// CF - #[allow(clippy::type_complexity)] #[cold] fn conflict_resolution( &mut self, - incompatibility: IncompDpId, - ) -> Result<(Id, IncompDpId), IncompDpId> { + incompatibility: IncompatId, + package_store: &PackageArena, + dependency_provider: &DP, + ) -> Result<(PackageId, IncompatId), IncompatId> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { if self.incompatibility_store[current_incompat_id] - .is_terminal(self.root_package, &self.root_version) + .is_terminal(self.root_package_id, self.root_version_index) { return Err(current_incompat_id); - } else { - let (package, satisfier_search_result) = self.partial_solution.satisfier_search( - &self.incompatibility_store[current_incompat_id], - &self.incompatibility_store, - ); - match satisfier_search_result { - SatisfierSearch::DifferentDecisionLevels { + } + + let (package_id, satisfier_search_result) = self.partial_solution.satisfier_search( + &self.incompatibility_store[current_incompat_id], + &self.incompatibility_store, + package_store, + ); + match satisfier_search_result { + SatisfierSearch::DifferentDecisionLevels { + previous_satisfier_level, + } => { + self.backtrack( + current_incompat_id, + current_incompat_changed, previous_satisfier_level, - } => { - self.backtrack( - current_incompat_id, - current_incompat_changed, - previous_satisfier_level, - ); - log::info!("backtrack to {:?}", previous_satisfier_level); - return Ok((package, current_incompat_id)); - } - SatisfierSearch::SameDecisionLevels { satisfier_cause } => { - let prior_cause = Incompatibility::prior_cause( - current_incompat_id, - satisfier_cause, - package, - &self.incompatibility_store, - ); - log::info!("prior cause: {}", prior_cause.display(&self.package_store)); - current_incompat_id = self.incompatibility_store.alloc(prior_cause); - current_incompat_changed = true; - } + ); + log::info!("backtrack to {:?}", previous_satisfier_level); + return Ok((package_id, current_incompat_id)); + } + SatisfierSearch::SameDecisionLevels { satisfier_cause } => { + let prior_cause = Incompatibility::prior_cause( + current_incompat_id, + satisfier_cause, + package_id, + &self.incompatibility_store, + ); + log::info!( + "prior cause: {}", + prior_cause.display(package_store, dependency_provider) + ); + current_incompat_id = self.incompatibility_store.push(prior_cause); + current_incompat_changed = true; } } } @@ -237,16 +232,13 @@ impl State { /// Backtracking. fn backtrack( &mut self, - incompat: IncompDpId, + incompat_id: IncompatId, incompat_changed: bool, decision_level: DecisionLevel, ) { self.partial_solution.backtrack(decision_level); - // Remove contradicted incompatibilities that depend on decisions we just backtracked away. - self.contradicted_incompatibilities - .retain(|_, dl| *dl <= decision_level); if incompat_changed { - self.merge_incompatibility(incompat); + self.merge_incompatibility(incompat_id); } } @@ -265,21 +257,29 @@ impl State { /// (provided that no other version of foo exists between 1.0.0 and 2.0.0). /// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 } /// without having to check the existence of other versions though. - fn merge_incompatibility(&mut self, mut id: IncompDpId) { - if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() { + fn merge_incompatibility(&mut self, mut id: IncompatId) { + fn get_or_default( + v: &mut Vec>, + package_id: PackageId, + ) -> &mut SmallVec<[IncompatId; 4]> { + let pkg_idx = package_id.get() as usize; + if pkg_idx + 1 > v.len() { + v.resize(pkg_idx + 1, SmallVec::new()); + } + &mut v[pkg_idx] + } + + if let Some((pid1, pid2)) = self.incompatibility_store[id].as_dependency() { // If we are a dependency, there's a good chance we can be merged with a previous dependency - let deps_lookup = self.merged_dependencies.entry((p1, p2)).or_default(); - if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| { + let deps_lookup = self.merged_dependencies.entry((pid1, pid2)).or_default(); + if let Some((past, merged)) = deps_lookup.iter_mut().find_map(|past| { self.incompatibility_store[id] .merge_dependents(&self.incompatibility_store[*past]) .map(|m| (past, m)) }) { - let new = self.incompatibility_store.alloc(merged); - for (pkg, _) in self.incompatibility_store[new].iter() { - self.incompatibilities - .entry(pkg) - .or_default() - .retain(|id| id != past); + let new = self.incompatibility_store.push(merged); + for (package_id, _) in self.incompatibility_store[new].iter() { + get_or_default(&mut self.incompatibilities, package_id).retain(|id| id != past); } *past = new; id = new; @@ -287,21 +287,16 @@ impl State { deps_lookup.push(id); } } - for (pkg, term) in self.incompatibility_store[id].iter() { - if cfg!(debug_assertions) { - assert_ne!(term, &crate::term::Term::any()); - } - self.incompatibilities.entry(pkg).or_default().push(id); + for (package_id, term) in self.incompatibility_store[id].iter() { + debug_assert_ne!(term, Term::any()); + get_or_default(&mut self.incompatibilities, package_id).push(id); } } // Error reporting ######################################################### - fn build_derivation_tree( - &self, - incompat: IncompDpId, - ) -> DerivationTree { - let mut all_ids: Set> = Set::default(); + fn build_derivation_tree(&self, incompat: IncompatId) -> DerivationTree { + let mut all_ids: Set = Set::default(); let mut shared_ids = Set::default(); let mut stack = vec![incompat]; while let Some(i) = stack.pop() { @@ -319,14 +314,13 @@ impl State { // That is to say we need to ensure that the causes are processed before the incompatibility they effect. // It happens to be that sorting by their ID maintains this property. let mut sorted_ids = all_ids.into_iter().collect::>(); - sorted_ids.sort_unstable_by_key(|id| id.into_raw()); + sorted_ids.sort_unstable_by_key(|id| id.0); let mut precomputed = Map::default(); for id in sorted_ids { let tree = Incompatibility::build_derivation_tree( id, &shared_ids, &self.incompatibility_store, - &self.package_store, &precomputed, ); precomputed.insert(id, Arc::new(tree)); diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 1417fb9e..1b4d1448 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -3,15 +3,114 @@ //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. -use std::fmt::{Debug, Display}; +use std::fmt::{self, Debug, Display}; +use std::ops::{Index, IndexMut, Range}; use std::sync::Arc; -use crate::internal::{Arena, HashArena, Id, SmallMap}; use crate::{ - term, DependencyProvider, DerivationTree, Derived, External, Map, Package, Set, Term, - VersionSet, + internal::{DecisionLevel, SmallMap}, + term, DefaultStringReportFormatter, DependencyProvider, DerivationTree, Derived, External, Map, + PackageArena, PackageId, ReportFormatter, Set, Term, VersionIndex, VersionSet, }; +/// Type for identifying incompatibilities. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub(crate) struct IncompatId(pub(crate) u32); + +impl IncompatId { + /// Convert `Range` to an iterator. + pub(crate) fn range_to_iter(range: Range) -> impl Iterator { + (range.start.0..range.end.0).map(IncompatId) + } +} + +/// Incompatibility arena +#[derive(Debug, Clone)] +pub(crate) struct IncompatArena(Vec>); + +impl IncompatArena { + /// Create an empty incompatibility arena. + pub(crate) fn new() -> Self { + Self(Default::default()) + } + + /// Push an incompatibility in the arena and get its identifier. + pub(crate) fn push(&mut self, incompatibility: Incompatibility) -> IncompatId { + let len = self.0.len(); + self.0.push(incompatibility); + IncompatId(len as u32) + } + + /// Appends incompatibilities to the arena and get their identifier. + pub(crate) fn extend( + &mut self, + incompatibilities: impl Iterator>, + ) -> Range { + let start = self.0.len(); + self.0.extend(incompatibilities); + let end = self.0.len(); + IncompatId(start as u32)..IncompatId(end as u32) + } +} + +impl Index for IncompatArena { + type Output = Incompatibility; + + fn index(&self, index: IncompatId) -> &Self::Output { + &self.0[index.0 as usize] + } +} + +impl IndexMut for IncompatArena { + fn index_mut(&mut self, index: IncompatId) -> &mut Self::Output { + &mut self.0[index.0 as usize] + } +} + +impl Index> for IncompatArena { + type Output = [Incompatibility]; + + fn index(&self, range: Range) -> &Self::Output { + &self.0[range.start.0 as usize..range.end.0 as usize] + } +} + +#[derive(Debug, Clone)] +struct ContradicationInfo { + /// Store the decision level when the incompatibility was found to be contradicted. + /// These will stay contradicted until we have backtracked beyond its associated decision level. + decision_level: DecisionLevel, + /// Store the backtrack generation for the decision level. + backtrack_generation: u32, +} + +impl ContradicationInfo { + /// Construct a new value. + fn new(decision_level: DecisionLevel, backtrack_generation: u32) -> Self { + Self { + decision_level, + backtrack_generation, + } + } + + /// Construct a value interpreted as not contradicted. + fn not_contradicted() -> Self { + Self { + decision_level: DecisionLevel::MAX, + backtrack_generation: 0, + } + } + + /// Check for contradiction. + fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool { + last_valid_decision_levels + .get(self.backtrack_generation as usize) + .map(|&l| self.decision_level <= l) + .unwrap_or(true) + } +} + /// An incompatibility is a set of terms for different packages /// that should never be satisfied all together. /// An incompatibility usually originates from a package dependency. @@ -28,32 +127,24 @@ use crate::{ /// during conflict resolution. More about all this in /// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility). #[derive(Debug, Clone)] -pub(crate) struct Incompatibility { - package_terms: SmallMap, Term>, - kind: Kind, +pub(crate) struct Incompatibility { + package_terms: SmallMap, + kind: Kind, + contradiction_info: ContradicationInfo, } -/// Type alias of unique identifiers for incompatibilities. -pub(crate) type IncompId = Id>; - -pub(crate) type IncompDpId = IncompId< - ::P, - ::VS, - ::M, ->; - #[derive(Debug, Clone)] -enum Kind { +enum Kind { /// Initial incompatibility aiming at picking the root package for the first decision. /// /// This incompatibility drives the resolution, it requires that we pick the (virtual) root /// packages. - NotRoot(Id

, VS::V), + NotRoot(PackageId, VersionIndex), /// There are no versions in the given range for this package. /// /// This incompatibility is used when we tried all versions in a range and no version /// worked, so we have to backtrack - NoVersions(Id

, VS), + NoVersions(PackageId, VersionSet), /// Incompatibility coming from the dependencies of a given package. /// /// If a@1 depends on b>=1,<2, we create an incompatibility with terms `{a 1, b <1,>=2}` with @@ -61,102 +152,117 @@ enum Kind { /// /// We can merge multiple dependents with the same version. For example, if a@1 depends on b and /// a@2 depends on b, we can say instead a@1||2 depends on b. - FromDependencyOf(Id

, VS, Id

, VS), + FromDependencyOf(PackageId, VersionSet, PackageId, VersionSet), /// Derived from two causes. Stores cause ids. /// /// For example, if a -> b and b -> c, we can derive a -> c. - DerivedFrom(IncompId, IncompId), + DerivedFrom(IncompatId, IncompatId), /// The package is unavailable for reasons outside pubgrub. /// /// Examples: /// * The version would require building the package, but builds are disabled. /// * The package is not available in the cache, but internet access has been disabled. - Custom(Id

, VS, M), + Custom(PackageId, VersionSet, M), } /// A Relation describes how a set of terms can be compared to an incompatibility. /// Typically, the set of terms comes from the partial solution. #[derive(Eq, PartialEq, Debug)] -pub(crate) enum Relation { +pub(crate) enum Relation { /// We say that a set of terms S satisfies an incompatibility I /// if S satisfies every term in I. Satisfied, /// We say that S contradicts I /// if S contradicts at least one term in I. - Contradicted(Id

), + Contradicted(PackageId), /// If S satisfies all but one of I's terms and is inconclusive for the remaining term, /// we say S "almost satisfies" I and we call the remaining term the "unsatisfied term". - AlmostSatisfied(Id

), + AlmostSatisfied(PackageId), /// Otherwise, we say that their relation is inconclusive. Inconclusive, } -impl Incompatibility { +impl Incompatibility { /// Create the initial "not Root" incompatibility. - pub(crate) fn not_root(package: Id

, version: VS::V) -> Self { + pub(crate) fn not_root(package_id: PackageId, version_index: VersionIndex) -> Self { Self { package_terms: SmallMap::One([( - package, - Term::Negative(VS::singleton(version.clone())), + package_id, + Term::negative(VersionSet::singleton(version_index)), )]), - kind: Kind::NotRoot(package, version), + kind: Kind::NotRoot(package_id, version_index), + contradiction_info: ContradicationInfo::not_contradicted(), } } /// Create an incompatibility to remember that a given set does not contain any version. - pub(crate) fn no_versions(package: Id

, term: Term) -> Self { - let set = match &term { - Term::Positive(r) => r.clone(), - Term::Negative(_) => panic!("No version should have a positive term"), + pub(crate) fn no_versions(package_id: PackageId, term: Term) -> Self { + let set = if term.is_positive() { + term.version_set() + } else { + panic!("No version should have a positive term") }; Self { - package_terms: SmallMap::One([(package, term)]), - kind: Kind::NoVersions(package, set), + package_terms: SmallMap::One([(package_id, term)]), + kind: Kind::NoVersions(package_id, set), + contradiction_info: ContradicationInfo::not_contradicted(), } } /// Create an incompatibility for a reason outside pubgrub. #[allow(dead_code)] // Used by uv - pub(crate) fn custom_term(package: Id

, term: Term, metadata: M) -> Self { - let set = match &term { - Term::Positive(r) => r.clone(), - Term::Negative(_) => panic!("No version should have a positive term"), + pub(crate) fn custom_term(package_id: PackageId, term: Term, metadata: M) -> Self { + let set = if term.is_positive() { + term.version_set() + } else { + panic!("No version should have a positive term") }; Self { - package_terms: SmallMap::One([(package, term)]), - kind: Kind::Custom(package, set, metadata), + package_terms: SmallMap::One([(package_id, term)]), + kind: Kind::Custom(package_id, set, metadata), + contradiction_info: ContradicationInfo::not_contradicted(), } } /// Create an incompatibility for a reason outside pubgrub. - pub(crate) fn custom_version(package: Id

, version: VS::V, metadata: M) -> Self { - let set = VS::singleton(version); - let term = Term::Positive(set.clone()); + pub(crate) fn custom_version( + package_id: PackageId, + version_index: VersionIndex, + metadata: M, + ) -> Self { + let set = VersionSet::singleton(version_index); + let term = Term::positive(set); Self { - package_terms: SmallMap::One([(package, term)]), - kind: Kind::Custom(package, set, metadata), + package_terms: SmallMap::One([(package_id, term)]), + kind: Kind::Custom(package_id, set, metadata), + contradiction_info: ContradicationInfo::not_contradicted(), } } /// Build an incompatibility from a given dependency. - pub(crate) fn from_dependency(package: Id

, versions: VS, dep: (Id

, VS)) -> Self { - let (p2, set2) = dep; + pub(crate) fn from_dependency( + package_id: PackageId, + vs: VersionSet, + dep: (PackageId, VersionSet), + ) -> Self { + let (pid2, set2) = dep; Self { - package_terms: if set2 == VS::empty() { - SmallMap::One([(package, Term::Positive(versions.clone()))]) + package_terms: if set2 == VersionSet::empty() { + SmallMap::One([(package_id, Term::positive(vs))]) } else { SmallMap::Two([ - (package, Term::Positive(versions.clone())), - (p2, Term::Negative(set2.clone())), + (package_id, Term::positive(vs)), + (pid2, Term::negative(set2)), ]) }, - kind: Kind::FromDependencyOf(package, versions, p2, set2), + kind: Kind::FromDependencyOf(package_id, vs, pid2, set2), + contradiction_info: ContradicationInfo::not_contradicted(), } } - pub(crate) fn as_dependency(&self) -> Option<(Id

, Id

)> { - match &self.kind { - Kind::FromDependencyOf(p1, _, p2, _) => Some((*p1, *p2)), + pub(crate) fn as_dependency(&self) -> Option<(PackageId, PackageId)> { + match self.kind { + Kind::FromDependencyOf(pid1, _, pid2, _) => Some((pid1, pid2)), _ => None, } } @@ -175,87 +281,105 @@ impl Incompatibilit debug_assert!(self.as_dependency().is_some()); // Check that both incompatibilities are of the shape p1 depends on p2, // with the same p1 and p2. - let self_pkgs = self.as_dependency()?; - if self_pkgs != other.as_dependency()? { + let self_package_ids = self.as_dependency()?; + if self_package_ids != other.as_dependency()? { return None; } - let (p1, p2) = self_pkgs; - let dep_term = self.get(p2); + let (pid1, pid2) = self_package_ids; + let dep_term = self.get(pid2); // The dependency range for p2 must be the same in both case // to be able to merge multiple p1 ranges. - if dep_term != other.get(p2) { + if dep_term != other.get(pid2) { return None; } Some(Self::from_dependency( - p1, - self.get(p1) + pid1, + self.get(pid1) .unwrap() .unwrap_positive() - .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here + .union(other.get(pid1).unwrap().unwrap_positive()), ( - p2, - dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()), + pid2, + dep_term.map_or(VersionSet::empty(), |v| v.unwrap_negative()), ), )) } /// Prior cause of two incompatibilities using the rule of resolution. pub(crate) fn prior_cause( - incompat: Id, - satisfier_cause: Id, - package: Id

, - incompatibility_store: &Arena, + incompat: IncompatId, + satisfier_cause: IncompatId, + package_id: PackageId, + incompatibility_store: &IncompatArena, ) -> Self { let kind = Kind::DerivedFrom(incompat, satisfier_cause); // Optimization to avoid cloning and dropping t1 let (t1, mut package_terms) = incompatibility_store[incompat] .package_terms - .split_one(&package) + .split_one(&package_id) .unwrap(); let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms; package_terms.merge( - satisfier_cause_terms.iter().filter(|(p, _)| p != &&package), - |t1, t2| Some(t1.intersection(t2)), + satisfier_cause_terms + .iter() + .filter(|(&pid, _)| pid != package_id), + |&t1, &t2| Some(t1.intersection(t2)), ); - let term = t1.union(satisfier_cause_terms.get(&package).unwrap()); + let term = t1.union(*satisfier_cause_terms.get(&package_id).unwrap()); if term != Term::any() { - package_terms.insert(package, term); + package_terms.insert(package_id, term); } Self { package_terms, kind, + contradiction_info: ContradicationInfo::not_contradicted(), } } /// Check if an incompatibility should mark the end of the algorithm /// because it satisfies the root package. - pub(crate) fn is_terminal(&self, root_package: Id

, root_version: &VS::V) -> bool { + pub(crate) fn is_terminal( + &self, + root_package_id: PackageId, + root_version_index: VersionIndex, + ) -> bool { if self.package_terms.len() == 0 { true } else if self.package_terms.len() > 1 { false } else { - let (package, term) = self.package_terms.iter().next().unwrap(); - (package == &root_package) && term.contains(root_version) + let (&package_id, term) = self.package_terms.iter().next().unwrap(); + (package_id == root_package_id) && term.contains(root_version_index) } } + /// Check if an incompatibility is contradicted. + pub(crate) fn is_contradicted(&self, last_valid_decision_levels: &[DecisionLevel]) -> bool { + self.contradiction_info + .is_contradicted(last_valid_decision_levels) + } + + /// Set incompatibility contradication info. + pub(crate) fn set_contradication_info( + &mut self, + decision_level: DecisionLevel, + backtrack_generation: u32, + ) { + self.contradiction_info = ContradicationInfo::new(decision_level, backtrack_generation); + } + /// Get the term related to a given package (if it exists). - pub(crate) fn get(&self, package: Id

) -> Option<&Term> { - self.package_terms.get(&package) + pub(crate) fn get(&self, package_id: PackageId) -> Option { + self.package_terms.get(&package_id).copied() } /// Iterate over packages. - pub(crate) fn iter(&self) -> impl Iterator, &Term)> { - self.package_terms - .iter() - .map(|(package, term)| (*package, term)) + pub(crate) fn iter(&self) -> impl Iterator + use<'_, M> { + self.package_terms.iter().map(|(&k, &v)| (k, v)) } - // Reporting ############################################################### - /// Retrieve parent causes if of type DerivedFrom. - pub(crate) fn causes(&self) -> Option<(Id, Id)> { + pub(crate) fn causes(&self) -> Option<(IncompatId, IncompatId)> { match self.kind { Kind::DerivedFrom(id1, id2) => Some((id1, id2)), _ => None, @@ -264,21 +388,16 @@ impl Incompatibilit /// Build a derivation tree for error reporting. pub(crate) fn build_derivation_tree( - self_id: Id, - shared_ids: &Set>, - store: &Arena, - package_store: &HashArena

, - precomputed: &Map, Arc>>, - ) -> DerivationTree { + self_id: IncompatId, + shared_ids: &Set, + store: &IncompatArena, + precomputed: &Map>>, + ) -> DerivationTree { match store[self_id].kind.clone() { Kind::DerivedFrom(id1, id2) => { - let derived: Derived = Derived { - terms: store[self_id] - .package_terms - .iter() - .map(|(&a, b)| (package_store[a].clone(), b.clone())) - .collect(), - shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()), + let derived = Derived { + terms: store[self_id].package_terms.as_map(), + shared_id: shared_ids.get(&self_id).map(|id| id.0 as usize), cause1: precomputed .get(&id1) .expect("Non-topological calls building tree") @@ -290,41 +409,34 @@ impl Incompatibilit }; DerivationTree::Derived(derived) } - Kind::NotRoot(package, version) => { - DerivationTree::External(External::NotRoot(package_store[package].clone(), version)) + Kind::NotRoot(package_id, version_index) => { + DerivationTree::External(External::NotRoot(package_id, version_index)) } - Kind::NoVersions(package, set) => DerivationTree::External(External::NoVersions( - package_store[package].clone(), - set.clone(), - )), - Kind::FromDependencyOf(package, set, dep_package, dep_set) => { + Kind::NoVersions(package_id, set) => { + DerivationTree::External(External::NoVersions(package_id, set)) + } + Kind::FromDependencyOf(package_id, set, dep_package_id, dep_set) => { DerivationTree::External(External::FromDependencyOf( - package_store[package].clone(), - set.clone(), - package_store[dep_package].clone(), - dep_set.clone(), + package_id, + set, + dep_package_id, + dep_set, )) } - Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom( - package_store[package].clone(), - set.clone(), - metadata.clone(), - )), + Kind::Custom(package_id, set, metadata) => { + DerivationTree::External(External::Custom(package_id, set, metadata.clone())) + } } } -} -impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> - Incompatibility -{ /// CF definition of Relation enum. - pub(crate) fn relation(&self, terms: impl Fn(Id

) -> Option<&'a Term>) -> Relation

{ + pub(crate) fn relation(&self, terms: impl Fn(PackageId) -> Option) -> Relation { let mut relation = Relation::Satisfied; - for (&package, incompat_term) in self.package_terms.iter() { - match terms(package).map(|term| incompat_term.relation_with(term)) { + for (&package_id, &incompat_term) in self.package_terms.iter() { + match terms(package_id).map(|term| incompat_term.relation_with(term)) { Some(term::Relation::Satisfied) => {} Some(term::Relation::Contradicted) => { - return Relation::Contradicted(package); + return Relation::Contradicted(package_id); } None | Some(term::Relation::Inconclusive) => { // If a package is not present, the intersection is the same as [Term::any]. @@ -333,7 +445,7 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> // but we systematically remove those from incompatibilities // so we're safe on that front. if relation == Relation::Satisfied { - relation = Relation::AlmostSatisfied(package); + relation = Relation::AlmostSatisfied(package_id); } else { return Relation::Inconclusive; } @@ -342,40 +454,47 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> } relation } -} -impl Incompatibility { - pub fn display<'a>(&'a self, package_store: &'a HashArena

) -> impl Display + 'a { - match self.iter().collect::>().as_slice() { - [] => "version solving failed".into(), - // TODO: special case when that unique package is root. - [(package, Term::Positive(range))] => { - format!("{} {} is forbidden", package_store[*package], range) - } - [(package, Term::Negative(range))] => { - format!("{} {} is mandatory", package_store[*package], range) - } - [(p_pos, Term::Positive(r_pos)), (p_neg, Term::Negative(r_neg))] - | [(p_neg, Term::Negative(r_neg)), (p_pos, Term::Positive(r_pos))] => { - External::<_, _, M>::FromDependencyOf( - &package_store[*p_pos], - r_pos.clone(), - &package_store[*p_neg], - r_neg.clone(), - ) - .to_string() - } - slice => { - let str_terms: Vec<_> = slice - .iter() - .map(|(p, t)| format!("{} {}", package_store[*p], t)) - .collect(); - str_terms.join(", ") + " are incompatible" - } + pub(crate) fn display<'a, DP: DependencyProvider>( + &'a self, + package_store: &'a PackageArena, + dependency_provider: &'a DP, + ) -> IncompatibilityDisplay<'a, DP, M> { + IncompatibilityDisplay { + incompatibility: self, + package_store, + dependency_provider, } } } +pub(crate) struct IncompatibilityDisplay< + 'a, + DP: DependencyProvider, + M: Eq + Clone + Debug + Display, +> { + incompatibility: &'a Incompatibility, + package_store: &'a PackageArena, + dependency_provider: &'a DP, +} + +impl Display + for IncompatibilityDisplay<'_, DP, M> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + ReportFormatter::::format_terms( + &DefaultStringReportFormatter, + &self.incompatibility.package_terms.as_map(), + self.package_store, + self.dependency_provider + ) + ) + } +} + // TESTS ####################################################################### #[cfg(test)] @@ -384,10 +503,8 @@ pub(crate) mod tests { use super::*; use crate::term::tests::strategy as term_strat; - use crate::Ranges; proptest! { - /// For any three different packages p1, p2 and p3, /// for any three terms t1, t2 and t3, /// if we have the two following incompatibilities: @@ -397,27 +514,25 @@ pub(crate) mod tests { /// { p1: t1, p3: t3 } #[test] fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) { - let mut store = Arena::new(); - let mut package_store = HashArena::new(); - let p1 = package_store.alloc("p1"); - let p2 = package_store.alloc("p2"); - let p3 = package_store.alloc("p3"); - let i1 = store.alloc(Incompatibility { - package_terms: SmallMap::Two([(p1, t1.clone()), (p2, t2.negate())]), - kind: Kind::<_, _, String>::FromDependencyOf(p1, Ranges::full(), p2, Ranges::full()) + let mut store = IncompatArena::new(); + let i1 = store.push(Incompatibility { + package_terms: SmallMap::Two([(PackageId(1), t1), (PackageId(2), t2.negate())]), + kind: Kind::::FromDependencyOf(PackageId(1), VersionSet::full(), PackageId(2), VersionSet::full()), + contradiction_info: ContradicationInfo::not_contradicted(), }); - let i2 = store.alloc(Incompatibility { - package_terms: SmallMap::Two([(p2, t2), (p3, t3.clone())]), - kind: Kind::<_, _, String>::FromDependencyOf(p2, Ranges::full(), p3, Ranges::full()) + let i2 = store.push(Incompatibility { + package_terms: SmallMap::Two([(PackageId(2), t2), (PackageId(3), t3)]), + kind: Kind::::FromDependencyOf(PackageId(2), VersionSet::full(), PackageId(3), VersionSet::full()), + contradiction_info: ContradicationInfo::not_contradicted(), }); let mut i3 = Map::default(); - i3.insert(p1, t1); - i3.insert(p3, t3); + i3.insert(PackageId(1), t1); + i3.insert(PackageId(3), t3); - let i_resolution = Incompatibility::prior_cause(i1, i2, p2, &store); - assert_eq!(i_resolution.package_terms.iter().map(|(&k, v)|(k, v.clone())).collect::>(), i3); + let i_resolution = Incompatibility::prior_cause(i1, i2, PackageId(2), &store); + assert_eq!(i_resolution.package_terms.as_map(), i3); } } diff --git a/src/internal/mod.rs b/src/internal/mod.rs index e10770d4..5e65cdcd 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -2,16 +2,12 @@ //! Non exposed modules. -mod arena; mod core; mod incompatibility; mod partial_solution; mod small_map; -mod small_vec; -pub(crate) use arena::{Arena, HashArena, Id}; pub(crate) use core::State; -pub(crate) use incompatibility::{IncompDpId, IncompId, Incompatibility, Relation}; +pub(crate) use incompatibility::{IncompatArena, IncompatId, Incompatibility, Relation}; pub(crate) use partial_solution::{DecisionLevel, PartialSolution, SatisfierSearch}; pub(crate) use small_map::SmallMap; -pub(crate) use small_vec::SmallVec; diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index a00b70d4..b310e712 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -1,26 +1,32 @@ // SPDX-License-Identifier: MPL-2.0 -//! A Memory acts like a structured partial solution -//! where terms are regrouped by package in a [Map](crate::type_aliases::Map). +//! A Memory acts like a structured partial solution where terms are regrouped by package in a [Map]. -use std::fmt::{Debug, Display}; +use std::fmt::{self, Debug, Display}; use std::hash::BuildHasherDefault; use priority_queue::PriorityQueue; use rustc_hash::FxHasher; +use smallvec::{smallvec, SmallVec}; -use crate::internal::{ - Arena, HashArena, Id, IncompDpId, IncompId, Incompatibility, Relation, SmallMap, SmallVec, +use crate::{ + internal::{IncompatArena, IncompatId, Incompatibility, Relation, SmallMap}, + DependencyProvider, FxIndexSet, Map, PackageArena, PackageId, SelectedDependencies, Term, + VersionIndex, VersionSet, }; -use crate::{DependencyProvider, Package, Term, VersionSet}; - -type FnvIndexMap = indexmap::IndexMap>; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub(crate) struct DecisionLevel(pub(crate) u32); +#[repr(transparent)] +pub(crate) struct DecisionLevel(u32); impl DecisionLevel { - pub(crate) fn increment(self) -> Self { + pub(crate) const MAX: Self = Self(u32::MAX); + + fn get(self) -> u32 { + self.0 + } + + fn increment(self) -> Self { Self(self.0 + 1) } } @@ -31,41 +37,73 @@ impl DecisionLevel { pub(crate) struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, - /// `package_assignments` is primarily a HashMap from a package to its - /// `PackageAssignments`. But it can also keep the items in an order. - /// We maintain three sections in this order: - /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`. - /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level. - /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments - /// changed since the last time `prioritize` has been called. Within this range there is no sorting. - /// 3. `[changed_this_decision_level..]` Contains all packages that **have** had there assignments changed since - /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range - /// did not have a change. Within this range there is no sorting. - #[allow(clippy::type_complexity)] - package_assignments: FnvIndexMap, PackageAssignments>, - /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment + package_assignments: Vec, + package_assignments_indices: Vec, + package_assignments_lengths: Vec, + /// A package is a potential pick if there isn't an already selected version (no "decision") + /// and if it contains at least one positive derivation term in the partial solution. + potential_picks: FxIndexSet, + /// `prioritized_potential_packages` is primarily a HashMap from a package with no decision and a positive assignment /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. prioritized_potential_packages: - PriorityQueue, DP::Priority, BuildHasherDefault>, - changed_this_decision_level: usize, - has_ever_backtracked: bool, + PriorityQueue>, + last_valid_decision_levels: Vec, +} + +impl PartialSolution { + pub(crate) fn display<'a>( + &'a self, + package_store: &'a PackageArena, + ) -> PartialSolutionDisplay<'a, DP> { + PartialSolutionDisplay { + partial_solution: self, + package_store, + } + } +} + +pub(crate) struct PartialSolutionDisplay<'a, DP: DependencyProvider> { + partial_solution: &'a PartialSolution, + package_store: &'a PackageArena, +} + +impl Display for PartialSolutionDisplay<'_, DP> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let partial_solution = self.partial_solution; + let mut assignments: Vec<_> = partial_solution + .package_assignments_indices + .iter() + .filter_map(|&pa_idx| { + let pa = partial_solution.package_assignments.get(pa_idx as usize)?; + let pid = pa.package_id; + let pn = self.package_store.pkg(pid).unwrap(); + Some(format!("{pn}: {pa}")) + }) + .collect(); + assignments.sort(); + write!( + f, + "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", + partial_solution.next_global_index, + partial_solution.current_decision_level, + assignments.join("\t\n") + ) + } } /// Package assignments contain the potential decision and derivations /// that have already been made for a given package, /// as well as the intersection of terms by all of these. #[derive(Clone, Debug)] -struct PackageAssignments { - smallest_decision_level: DecisionLevel, +struct PackageAssignments { + package_id: PackageId, highest_decision_level: DecisionLevel, - dated_derivations: SmallVec>, - assignments_intersection: AssignmentsIntersection, + dated_derivations: SmallVec<[DatedDerivation; 1]>, + assignments_intersection: AssignmentsIntersection, } -impl Display - for PackageAssignments -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Display for PackageAssignments { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let derivations: Vec<_> = self .dated_derivations .iter() @@ -73,8 +111,7 @@ impl Display .collect(); write!( f, - "decision range: {:?}..{:?}\nderivations:\n {}\n,assignments_intersection: {}", - self.smallest_decision_level, + "highest_decision_level: {:?}\nderivations:\n {}\n,assignments_intersection: {}", self.highest_decision_level, derivations.join("\n "), self.assignments_intersection @@ -83,184 +120,172 @@ impl Display } #[derive(Clone, Debug)] -struct DatedDerivation { +struct DatedDerivation { global_index: u32, decision_level: DecisionLevel, - cause: IncompId, - accumulated_intersection: Term, + cause: IncompatId, + accumulated_intersection: Term, } -impl Display - for DatedDerivation -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Display for DatedDerivation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause) } } -#[derive(Clone, Debug)] -enum AssignmentsIntersection { - Decision((u32, VS::V, Term)), - Derivations(Term), +#[derive(Clone, Debug, Default)] +struct AssignmentsIntersection { + term: Term, + is_decision: bool, + decision_global_index: u32, + decision_version_index: VersionIndex, } -impl Display for AssignmentsIntersection { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Decision((lvl, version, _)) => { - write!(f, "Decision: level {}, v = {}", lvl, version) - } - Self::Derivations(term) => write!(f, "Derivations term: {}", term), +impl AssignmentsIntersection { + fn decision(global_index: u32, version_index: VersionIndex) -> Self { + Self { + term: Term::exact(version_index), + is_decision: true, + decision_global_index: global_index, + decision_version_index: version_index, + } + } + + fn derivations(term: Term) -> Self { + Self { + term, + ..Default::default() + } + } +} + +impl Display for AssignmentsIntersection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_decision { + let lvl = self.decision_global_index; + let version = self.decision_version_index.get(); + write!(f, "Decision: level {lvl}, v = {version}") + } else { + let term = self.term; + write!(f, "Derivations term: {term}") } } } #[derive(Clone, Debug)] -pub(crate) enum SatisfierSearch { +pub(crate) enum SatisfierSearch { DifferentDecisionLevels { previous_satisfier_level: DecisionLevel, }, SameDecisionLevels { - satisfier_cause: IncompId, + satisfier_cause: IncompatId, }, } -type SatisfiedMap = SmallMap, (Option>, u32, DecisionLevel)>; +type SatisfiedMap = SmallMap, u32, DecisionLevel)>; impl PartialSolution { - /// Initialize an empty PartialSolution. - pub(crate) fn empty() -> Self { + /// Initialize an empty `PartialSolution`. + pub(crate) fn empty(root_package: PackageId) -> Self { Self { next_global_index: 0, current_decision_level: DecisionLevel(0), - package_assignments: FnvIndexMap::default(), + potential_picks: FxIndexSet::default(), + package_assignments: Vec::new(), + package_assignments_indices: vec![u32::MAX; root_package.get() as usize + 1], + package_assignments_lengths: Vec::new(), prioritized_potential_packages: PriorityQueue::default(), - changed_this_decision_level: 0, - has_ever_backtracked: false, + last_valid_decision_levels: vec![DecisionLevel(0)], } } - pub(crate) fn display<'a>(&'a self, package_store: &'a HashArena) -> impl Display + 'a { - struct PSDisplay<'a, DP: DependencyProvider>(&'a PartialSolution, &'a HashArena); - - impl Display for PSDisplay<'_, DP> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut assignments: Vec<_> = self - .0 - .package_assignments - .iter() - .map(|(p, pa)| format!("{:?} = '{}': {}", p, self.1[*p], pa)) - .collect(); - assignments.sort(); - write!( - f, - "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", - self.0.next_global_index, - self.0.current_decision_level, - assignments.join("\t\n") - ) - } - } + /// Check if an incompatibility is contradicted. + pub(crate) fn is_contradicted(&self, incompat: &Incompatibility) -> bool { + incompat.is_contradicted(&self.last_valid_decision_levels) + } - PSDisplay(self, package_store) + /// Contradict an incompatibility. + pub(crate) fn contradict(&self, incompat: &mut Incompatibility) { + incompat.set_contradication_info( + self.current_decision_level, + self.last_valid_decision_levels.len() as u32, + ); } /// Add a decision. - pub(crate) fn add_decision(&mut self, package: Id, version: DP::V) { + pub(crate) fn add_decision(&mut self, package_id: PackageId, version_index: VersionIndex) { + let len = self.package_assignments.len(); + let pa = &mut self + .package_assignments + .get_mut(self.package_assignments_indices[package_id.get() as usize] as usize) + .expect("Derivations must already exist"); + // Check that add_decision is never used in the wrong context. if cfg!(debug_assertions) { - match self.package_assignments.get_mut(&package) { - None => panic!("Derivations must already exist"), - Some(pa) => match &pa.assignments_intersection { - // Cannot be called when a decision has already been taken. - AssignmentsIntersection::Decision(_) => panic!("Already existing decision"), - // Cannot be called if the versions is not contained in the terms' intersection. - AssignmentsIntersection::Derivations(term) => { - debug_assert!( - term.contains(&version), - "{:?}: {} was expected to be contained in {}", - package, - version, - term, - ) - } - }, - } - assert_eq!( - self.changed_this_decision_level, - self.package_assignments.len() + let pai = &pa.assignments_intersection; + // Cannot be called when a decision has already been taken. + assert!(!pai.is_decision, "Already existing decision"); + // Cannot be called if the versions is not contained in the terms' intersection. + assert!( + pai.term.contains(version_index), + "{} was expected to be contained in {}", + version_index.get(), + pai.term, ); + assert!(self.potential_picks.is_empty()); } - let new_idx = self.current_decision_level.0 as usize; + + self.package_assignments_lengths.push(len as u32); self.current_decision_level = self.current_decision_level.increment(); - let (old_idx, _, pa) = self - .package_assignments - .get_full_mut(&package) - .expect("Derivations must already exist"); pa.highest_decision_level = self.current_decision_level; - pa.assignments_intersection = AssignmentsIntersection::Decision(( - self.next_global_index, - version.clone(), - Term::exact(version), - )); - // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order. - if new_idx != old_idx { - self.package_assignments.swap_indices(new_idx, old_idx); - } + pa.assignments_intersection = + AssignmentsIntersection::decision(self.next_global_index, version_index); self.next_global_index += 1; } /// Add a derivation. pub(crate) fn add_derivation( &mut self, - package: Id, - cause: IncompDpId, - store: &Arena>, + package_id: PackageId, + cause: IncompatId, + incompat_term: Term, ) { - use indexmap::map::Entry; let mut dated_derivation = DatedDerivation { global_index: self.next_global_index, decision_level: self.current_decision_level, cause, - accumulated_intersection: store[cause].get(package).unwrap().negate(), + accumulated_intersection: incompat_term.negate(), }; self.next_global_index += 1; - let pa_last_index = self.package_assignments.len().saturating_sub(1); - match self.package_assignments.entry(package) { - Entry::Occupied(mut occupied) => { - let idx = occupied.index(); - let pa = occupied.get_mut(); - pa.highest_decision_level = self.current_decision_level; - match &mut pa.assignments_intersection { - // Check that add_derivation is never called in the wrong context. - AssignmentsIntersection::Decision(_) => { - panic!("add_derivation should not be called after a decision") - } - AssignmentsIntersection::Derivations(t) => { - *t = t.intersection(&dated_derivation.accumulated_intersection); - dated_derivation.accumulated_intersection = t.clone(); - if t.is_positive() { - // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1 - // but the copying is slower then the larger search - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, idx); - } - } - } - pa.dated_derivations.push(dated_derivation); + + self.resize_package_assignments_indices(package_id); + let pa_idx = &mut self.package_assignments_indices[package_id.get() as usize]; + + if let Some(pa) = self.package_assignments.get_mut(*pa_idx as usize) { + pa.highest_decision_level = self.current_decision_level; + // Check that add_derivation is never called in the wrong context. + assert!( + !pa.assignments_intersection.is_decision, + "add_derivation should not be called after a decision", + ); + let term = &mut pa.assignments_intersection.term; + *term = term.intersection(dated_derivation.accumulated_intersection); + dated_derivation.accumulated_intersection = *term; + pa.dated_derivations.push(dated_derivation); + if term.is_positive() { + self.potential_picks.insert(package_id); } - Entry::Vacant(v) => { - let term = dated_derivation.accumulated_intersection.clone(); - if term.is_positive() { - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, pa_last_index); - } - v.insert(PackageAssignments { - smallest_decision_level: self.current_decision_level, - highest_decision_level: self.current_decision_level, - dated_derivations: SmallVec::One([dated_derivation]), - assignments_intersection: AssignmentsIntersection::Derivations(term), - }); + } else { + let term = dated_derivation.accumulated_intersection; + *pa_idx = self.package_assignments.len() as u32; + self.package_assignments.push(PackageAssignments { + package_id, + highest_decision_level: self.current_decision_level, + dated_derivations: smallvec![dated_derivation], + assignments_intersection: AssignmentsIntersection::derivations(term), + }); + if term.is_positive() { + self.potential_picks.insert(package_id); } } } @@ -268,71 +293,82 @@ impl PartialSolution { #[cold] pub(crate) fn pick_highest_priority_pkg( &mut self, - prioritizer: impl Fn(Id, &DP::VS) -> DP::Priority, - ) -> Option> { - let check_all = self.changed_this_decision_level - == self.current_decision_level.0.saturating_sub(1) as usize; - let current_decision_level = self.current_decision_level; - let prioritized_potential_packages = &mut self.prioritized_potential_packages; - self.package_assignments - .get_range(self.changed_this_decision_level..) - .unwrap() - .iter() - .filter(|(_, pa)| { - // We only actually need to update the package if it has been changed - // since the last time we called prioritize. - // Which means it's highest decision level is the current decision level, - // or if we backtracked in the meantime. - check_all || pa.highest_decision_level == current_decision_level - }) - .filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p)) - .for_each(|(p, r)| { - let priority = prioritizer(p, r); - prioritized_potential_packages.push(p, priority); - }); - self.changed_this_decision_level = self.package_assignments.len(); - prioritized_potential_packages.pop().map(|(p, _)| p) + mut prioritizer: impl FnMut(PackageId, VersionSet) -> DP::Priority, + ) -> Option { + self.prioritized_potential_packages + .extend(self.potential_picks.iter().map(|&pid| { + let vs = self + .package_assignments + .get(self.package_assignments_indices[pid.get() as usize] as usize) + .expect("potential picks should have valid assignments") + .assignments_intersection + .term + .version_set(); + + (pid, prioritizer(pid, vs)) + })); + self.potential_picks.clear(); + self.prioritized_potential_packages + .pop() + .map(|(pid, _)| pid) } /// If a partial solution has, for every positive derivation, /// a corresponding decision that satisfies that assignment, /// it's a total solution and version solving has succeeded. - pub(crate) fn extract_solution(&self) -> impl Iterator, DP::V)> + '_ { - self.package_assignments + pub(crate) fn extract_solution( + &self, + package_store: PackageArena, + ) -> SelectedDependencies { + let used = self + .package_assignments_indices .iter() - .take(self.current_decision_level.0 as usize) - .map(|(&p, pa)| match &pa.assignments_intersection { - AssignmentsIntersection::Decision((_, v, _)) => (p, v.clone()), - AssignmentsIntersection::Derivations(_) => { - panic!("Derivations in the Decision part") + .filter_map(|&pa_idx| { + let pa = self.package_assignments.get(pa_idx as usize)?; + if !pa.assignments_intersection.is_decision { + return None; } + let version_index = pa.assignments_intersection.decision_version_index; + Some((pa.package_id, version_index)) }) + .collect::>(); + + package_store + .into_iter() + .enumerate() + .filter_map(|(i, p)| used.get(&PackageId(i as u32)).map(|&v| (p, v))) + .collect() } /// Backtrack the partial solution to a given decision level. pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; - self.package_assignments.retain(|_, pa| { - if pa.smallest_decision_level > decision_level { - // Remove all entries that have a smallest decision level higher than the backtrack target. - false - } else if pa.highest_decision_level <= decision_level { - // Do not change entries older than the backtrack decision level target. - true - } else { - // smallest_decision_level <= decision_level < highest_decision_level - // + self.potential_picks.clear(); + + let max_len = self.package_assignments_lengths[decision_level.get() as usize] as usize; + + for pa in &self.package_assignments[max_len..] { + self.package_assignments_indices[pa.package_id.get() as usize] = u32::MAX; + } + + self.package_assignments_lengths + .truncate(decision_level.get() as usize); + + self.package_assignments.truncate(max_len); + + for pa in &mut self.package_assignments { + if pa.highest_decision_level > decision_level { // Since decision_level < highest_decision_level, - // We can be certain that there will be no decision in this package assignments + // we can be certain that there will be no decision in this package assignments // after backtracking, because such decision would have been the last // assignment and it would have the "highest_decision_level". // Truncate the history. - while pa.dated_derivations.last().map(|dd| dd.decision_level) > Some(decision_level) - { - pa.dated_derivations.pop(); - } - debug_assert!(!pa.dated_derivations.is_empty()); + let last_idx = pa + .dated_derivations + .partition_point(|dd| dd.decision_level <= decision_level); + + pa.dated_derivations.truncate(last_idx); let last = pa.dated_derivations.last().unwrap(); @@ -341,14 +377,27 @@ impl PartialSolution { // Reset the assignments intersection. pa.assignments_intersection = - AssignmentsIntersection::Derivations(last.accumulated_intersection.clone()); - true + AssignmentsIntersection::derivations(last.accumulated_intersection); } - }); + + let pai = &pa.assignments_intersection; + if !pai.is_decision && pai.term.is_positive() { + self.potential_picks.insert(pa.package_id); + } + } + // Throw away all stored priority levels, And mark that they all need to be recomputed. self.prioritized_potential_packages.clear(); - self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; - self.has_ever_backtracked = true; + + // Update list of last valid contradicted decision levels + self.last_valid_decision_levels + .push(DecisionLevel(u32::MAX)); + + let index = self + .last_valid_decision_levels + .partition_point(|&l| l <= decision_level); + + self.last_valid_decision_levels[index..].fill(decision_level); } /// We can add the version to the partial solution as a decision @@ -358,26 +407,40 @@ impl PartialSolution { /// is already in the partial solution with an incompatible version. pub(crate) fn add_version( &mut self, - package: Id, - version: DP::V, - new_incompatibilities: std::ops::Range>, - store: &Arena>, + package_id: PackageId, + version_index: VersionIndex, + new_incompatibilities: std::ops::Range, + store: &IncompatArena, + package_store: &PackageArena, + dependency_provider: &DP, ) { - if !self.has_ever_backtracked { + let max_idx = store[new_incompatibilities.clone()] + .iter() + .flat_map(|incompat| incompat.iter().map(|(p, _)| p.0)) + .max() + .unwrap_or(0); + + self.resize_package_assignments_indices(PackageId(max_idx)); + + if self.last_valid_decision_levels.len() == 1 { // Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. // So let's live with a little bit of risk and add the decision without checking the dependencies. // The worst that can happen is we will have to do a full backtrack which only removes this one decision. - log::info!("add_decision: {package:?} @ {version} without checking dependencies"); - self.add_decision(package, version); + log::info!( + "add_decision: {} without checking dependencies", + dependency_provider + .package_version_display(package_store.pkg(package_id).unwrap(), version_index) + ); + self.add_decision(package_id, version_index); } else { // Check if any of the new dependencies preclude deciding on this crate version. - let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { - incompat.relation(|p| { - if p == package { - Some(&exact) + let exact = Term::exact(version_index); + let not_satisfied = |incompat: &Incompatibility| { + incompat.relation(|pid| { + if pid == package_id { + Some(exact) } else { - self.term_intersection_for_package(p) + self.term_intersection_for_package(pid) } }) != Relation::Satisfied }; @@ -385,50 +448,63 @@ impl PartialSolution { // Check none of the dependencies (new_incompatibilities) // would create a conflict (be satisfied). if store[new_incompatibilities].iter().all(not_satisfied) { - log::info!("add_decision: {package:?} @ {version}"); - self.add_decision(package, version); + log::info!( + "add_decision: {}", + dependency_provider.package_version_display( + package_store.pkg(package_id).unwrap(), + version_index + ) + ); + self.add_decision(package_id, version_index); } else { - log::info!("not adding {package:?} @ {version} because of its dependencies",); + log::info!( + "not adding {} because of its dependencies", + dependency_provider.package_version_display( + package_store.pkg(package_id).unwrap(), + version_index + ) + ); } } } + fn resize_package_assignments_indices(&mut self, package: PackageId) { + let idx = package.get() as usize; + if idx + 1 > self.package_assignments_indices.len() { + self.package_assignments_indices.resize(idx + 1, u32::MAX); + } + } + /// Check if the terms in the partial solution satisfy the incompatibility. - pub(crate) fn relation( - &self, - incompat: &Incompatibility, - ) -> Relation { - incompat.relation(|package| self.term_intersection_for_package(package)) + pub(crate) fn relation(&self, incompat: &Incompatibility) -> Relation { + incompat.relation(|package_id| self.term_intersection_for_package(package_id)) } /// Retrieve intersection of terms related to package. - pub(crate) fn term_intersection_for_package( - &self, - package: Id, - ) -> Option<&Term> { + pub(crate) fn term_intersection_for_package(&self, package_id: PackageId) -> Option { self.package_assignments - .get(&package) - .map(|pa| pa.assignments_intersection.term()) + .get(self.package_assignments_indices[package_id.get() as usize] as usize) + .map(|pa| pa.assignments_intersection.term) } /// Figure out if the satisfier and previous satisfier are of different decision levels. - #[allow(clippy::type_complexity)] pub(crate) fn satisfier_search( &self, - incompat: &Incompatibility, - store: &Arena>, - ) -> (Id, SatisfierSearch) { - let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); - let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map + incompat: &Incompatibility, + store: &IncompatArena, + package_store: &PackageArena, + ) -> (PackageId, SatisfierSearch) { + let satisfied_map = self.find_satisfier(incompat, package_store); + let (&satisfier_pid, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() - .max_by_key(|(_p, (_, global_index, _))| global_index) + .max_by_key(|(_, (_, global_index, _))| global_index) .unwrap(); - let previous_satisfier_level = Self::find_previous_satisfier( + let previous_satisfier_level = self.find_previous_satisfier( incompat, - satisfier_package, + satisfier_pid, satisfied_map, - &self.package_assignments, store, + package_store, ); let search_result = if previous_satisfier_level >= satisfier_decision_level { SatisfierSearch::SameDecisionLevels { @@ -439,7 +515,7 @@ impl PartialSolution { previous_satisfier_level, } }; - (satisfier_package, search_result) + (satisfier_pid, search_result) } /// A satisfier is the earliest assignment in partial solution such that the incompatibility @@ -451,15 +527,20 @@ impl PartialSolution { /// Question: This is possible since we added a "global_index" to every dated_derivation. /// It would be nice if we could get rid of it, but I don't know if then it will be possible /// to return a coherent previous_satisfier_level. - #[allow(clippy::type_complexity)] fn find_satisfier( - incompat: &Incompatibility, - package_assignments: &FnvIndexMap, PackageAssignments>, - ) -> SatisfiedMap { + &self, + incompat: &Incompatibility, + package_store: &PackageArena, + ) -> SatisfiedMap { let mut satisfied = SmallMap::Empty; - for (package, incompat_term) in incompat.iter() { - let pa = package_assignments.get(&package).expect("Must exist"); - satisfied.insert(package, pa.satisfier(package, &incompat_term.negate())); + for (package_id, incompat_term) in incompat.iter() { + let satisfied_info = self + .package_assignments + .get(self.package_assignments_indices[package_id.get() as usize] as usize) + .unwrap() + .satisfier::(package_id, incompat_term.negate(), package_store); + + satisfied.insert(package_id, satisfied_info); } satisfied } @@ -467,36 +548,42 @@ impl PartialSolution { /// Earliest assignment in the partial solution before satisfier /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. - #[allow(clippy::type_complexity)] fn find_previous_satisfier( - incompat: &Incompatibility, - satisfier_package: Id, - mut satisfied_map: SatisfiedMap, - package_assignments: &FnvIndexMap, PackageAssignments>, - store: &Arena>, + &self, + incompat: &Incompatibility, + satisfier_pid: PackageId, + mut satisfied_map: SatisfiedMap, + store: &IncompatArena, + package_store: &PackageArena, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. - let satisfier_pa = package_assignments.get(&satisfier_package).unwrap(); - let (satisfier_cause, _gidx, _dl) = satisfied_map.get(&satisfier_package).unwrap(); + let satisfier_pa = self + .package_assignments + .get(self.package_assignments_indices[satisfier_pid.get() as usize] as usize) + .unwrap(); + + let satisfier_cause = satisfied_map.get(&satisfier_pid).unwrap().0; - let accum_term = if let &Some(cause) = satisfier_cause { - store[cause].get(satisfier_package).unwrap().negate() + let accum_term = if let Some(cause) = satisfier_cause { + store[cause].get(satisfier_pid).unwrap().negate() } else { - match &satisfier_pa.assignments_intersection { - AssignmentsIntersection::Derivations(_) => panic!("must be a decision"), - AssignmentsIntersection::Decision((_, _, term)) => term.clone(), - } + assert!( + satisfier_pa.assignments_intersection.is_decision, + "must be a decision", + ); + satisfier_pa.assignments_intersection.term }; let incompat_term = incompat - .get(satisfier_package) + .get(satisfier_pid) .expect("satisfier package not in incompat"); satisfied_map.insert( - satisfier_package, - satisfier_pa.satisfier( - satisfier_package, - &accum_term.intersection(&incompat_term.negate()), + satisfier_pid, + satisfier_pa.satisfier::( + satisfier_pid, + accum_term.intersection(incompat_term.negate()), + package_store, ), ); @@ -507,73 +594,43 @@ impl PartialSolution { .unwrap(); decision_level.max(DecisionLevel(1)) } - - pub(crate) fn current_decision_level(&self) -> DecisionLevel { - self.current_decision_level - } } -impl PackageAssignments { - fn satisfier( +impl PackageAssignments { + fn satisfier( &self, - package: Id

, - start_term: &Term, - ) -> (Option>, u32, DecisionLevel) { - let empty = Term::empty(); + package_id: PackageId, + start_term: Term, + package_store: &PackageArena, + ) -> (Option, u32, DecisionLevel) { // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. let idx = self .dated_derivations - .as_slice() .partition_point(|dd| !dd.accumulated_intersection.is_disjoint(start_term)); if let Some(dd) = self.dated_derivations.get(idx) { - debug_assert_eq!(dd.accumulated_intersection.intersection(start_term), empty); + debug_assert_eq!( + dd.accumulated_intersection.intersection(start_term), + Term::empty(), + ); return (Some(dd.cause), dd.global_index, dd.decision_level); } - // If it wasn't found in the derivations, - // it must be the decision which is last (if called in the right context). - match &self.assignments_intersection { - AssignmentsIntersection::Decision((global_index, _, _)) => { - (None, *global_index, self.highest_decision_level) - } - AssignmentsIntersection::Derivations(accumulated_intersection) => { - unreachable!( - concat!( - "while processing package {:?}: ", - "accum_term = {} has overlap with incompat_term = {}, ", - "which means the last assignment should have been a decision, ", - "but instead it was a derivation. This shouldn't be possible! ", - "(Maybe your Version ordering is broken?)" - ), - package, accumulated_intersection, start_term - ) - } - } - } -} - -impl AssignmentsIntersection { - /// Returns the term intersection of all assignments (decision included). - fn term(&self) -> &Term { - match self { - Self::Decision((_, _, term)) => term, - Self::Derivations(term) => term, - } - } - - /// A package is a potential pick if there isn't an already - /// selected version (no "decision") - /// and if it contains at least one positive derivation term - /// in the partial solution. - fn potential_package_filter(&self, package: Id

) -> Option<(Id

, &VS)> { - match self { - Self::Decision(_) => None, - Self::Derivations(term_intersection) => { - if term_intersection.is_positive() { - Some((package, term_intersection.unwrap_positive())) - } else { - None - } - } + // If it wasn't found in the derivations, it must be the decision which is last (if called in the right context). + let AssignmentsIntersection { + term, + is_decision, + decision_global_index, + .. + } = self.assignments_intersection; + if !is_decision { + let p = package_store.pkg(package_id).unwrap(); + unreachable!( + "while processing package {p}: \ + accum_term = {term} has overlap with incompat_term = {start_term}, \ + which means the last assignment should have been a decision, \ + but instead it was a derivation. This shouldn't be possible! \ + (Maybe your Version ordering is broken?)" + ) } + (None, decision_global_index, self.highest_decision_level) } } diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index ed66409a..93952500 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -105,9 +105,12 @@ impl SmallMap { /// /// This is an optimization over the following, where we only need a reference to `t1`. It /// avoids cloning and then drop the ranges in each `prior_cause` call. - /// ```ignore + /// ``` + /// # use pubgrub::Map; + /// # let package_terms = Map::from_iter([(1, 1)]); + /// # let package = 1; /// let mut package_terms = package_terms.clone(); - // let t1 = package_terms.remove(package).unwrap(); + /// let t1 = package_terms.remove(&package).unwrap(); /// ``` pub(crate) fn split_one(&self, key: &K) -> Option<(&V, Self)> where @@ -190,6 +193,27 @@ impl SmallMap { } } +impl SmallMap { + pub(crate) fn as_map(&self) -> Map { + match self { + Self::Empty => Map::default(), + Self::One([(k, v)]) => { + let mut map = Map::with_capacity_and_hasher(1, Default::default()); + map.insert(k.clone(), v.clone()); + map + } + Self::Two(data) => { + let mut map = Map::with_capacity_and_hasher(2, Default::default()); + for (k, v) in data { + map.insert(k.clone(), v.clone()); + } + map + } + Self::Flexible(data) => data.clone(), + } + } +} + enum IterSmallMap<'a, K, V> { Inline(std::slice::Iter<'a, (K, V)>), Map(std::collections::hash_map::Iter<'a, K, V>), diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs deleted file mode 100644 index 621fc9bf..00000000 --- a/src/internal/small_vec.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::ops::Deref; - -#[derive(Clone)] -pub enum SmallVec { - Empty, - One([T; 1]), - Two([T; 2]), - Flexible(Vec), -} - -impl SmallVec { - pub fn empty() -> Self { - Self::Empty - } - - pub fn one(t: T) -> Self { - Self::One([t]) - } - - pub fn as_slice(&self) -> &[T] { - match self { - Self::Empty => &[], - Self::One(v) => v, - Self::Two(v) => v, - Self::Flexible(v) => v, - } - } - - pub fn as_mut_slice(&mut self) -> &mut [T] { - match self { - Self::Empty => &mut [], - Self::One(v) => v, - Self::Two(v) => v, - Self::Flexible(v) => v, - } - } - - pub fn push(&mut self, new: T) { - *self = match std::mem::take(self) { - Self::Empty => Self::One([new]), - Self::One([v1]) => Self::Two([v1, new]), - Self::Two([v1, v2]) => Self::Flexible(vec![v1, v2, new]), - Self::Flexible(mut v) => { - v.push(new); - Self::Flexible(v) - } - } - } - - pub fn pop(&mut self) -> Option { - match std::mem::take(self) { - Self::Empty => None, - Self::One([v1]) => { - *self = Self::Empty; - Some(v1) - } - Self::Two([v1, v2]) => { - *self = Self::One([v1]); - Some(v2) - } - Self::Flexible(mut v) => { - let out = v.pop(); - *self = Self::Flexible(v); - out - } - } - } - - pub fn clear(&mut self) { - if let Self::Flexible(mut v) = std::mem::take(self) { - v.clear(); - *self = Self::Flexible(v); - } // else: self already eq Empty from the take - } - - pub fn iter(&self) -> std::slice::Iter<'_, T> { - self.as_slice().iter() - } -} - -impl Default for SmallVec { - fn default() -> Self { - Self::Empty - } -} - -impl Deref for SmallVec { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl<'a, T> IntoIterator for &'a SmallVec { - type Item = &'a T; - - type IntoIter = std::slice::Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl Eq for SmallVec {} - -impl PartialEq for SmallVec { - fn eq(&self, other: &Self) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl fmt::Debug for SmallVec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_slice().fmt(f) - } -} - -impl Hash for SmallVec { - fn hash(&self, state: &mut H) { - self.len().hash(state); - Hash::hash_slice(self.as_slice(), state); - } -} - -#[cfg(feature = "serde")] -impl serde::Serialize for SmallVec { - fn serialize(&self, s: S) -> Result { - serde::Serialize::serialize(self.as_slice(), s) - } -} - -#[cfg(feature = "serde")] -impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec { - fn deserialize>(d: D) -> Result { - struct SmallVecVisitor { - marker: std::marker::PhantomData, - } - - impl<'de, T> serde::de::Visitor<'de> for SmallVecVisitor - where - T: serde::Deserialize<'de>, - { - type Value = SmallVec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut values = SmallVec::empty(); - while let Some(value) = seq.next_element()? { - values.push(value); - } - Ok(values) - } - } - - let visitor = SmallVecVisitor { - marker: Default::default(), - }; - d.deserialize_seq(visitor) - } -} - -impl IntoIterator for SmallVec { - type Item = T; - type IntoIter = SmallVecIntoIter; - - fn into_iter(self) -> Self::IntoIter { - match self { - SmallVec::Empty => SmallVecIntoIter::Empty, - SmallVec::One(a) => SmallVecIntoIter::One(a.into_iter()), - SmallVec::Two(a) => SmallVecIntoIter::Two(a.into_iter()), - SmallVec::Flexible(v) => SmallVecIntoIter::Flexible(v.into_iter()), - } - } -} - -pub enum SmallVecIntoIter { - Empty, - One(<[T; 1] as IntoIterator>::IntoIter), - Two(<[T; 2] as IntoIterator>::IntoIter), - Flexible( as IntoIterator>::IntoIter), -} - -impl Iterator for SmallVecIntoIter { - type Item = T; - - fn next(&mut self) -> Option { - match self { - SmallVecIntoIter::Empty => None, - SmallVecIntoIter::One(it) => it.next(), - SmallVecIntoIter::Two(it) => it.next(), - SmallVecIntoIter::Flexible(it) => it.next(), - } - } -} - -// TESTS ####################################################################### - -#[cfg(test)] -pub mod tests { - use proptest::prelude::*; - - use super::*; - - proptest! { - #[test] - fn push_and_pop(commands: Vec>) { - let mut v = vec![]; - let mut sv = SmallVec::Empty; - for command in commands { - match command { - Some(i) => { - v.push(i); - sv.push(i); - } - None => { - assert_eq!(v.pop(), sv.pop()); - } - } - assert_eq!(v.as_slice(), sv.as_slice()); - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index cc1c943f..2e924fcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,20 +8,10 @@ //! we should try to provide a very human-readable and clear //! explanation as to why that failed. //! -//! # Package and Version traits +//! # Packages and versions //! -//! All the code in this crate is manipulating packages and versions, and for this to work -//! we defined a [Package] trait -//! that is used as bounds on most of the exposed types and functions. +//! TODO: doc //! -//! Package identifiers needs to implement our [Package] trait, -//! which is automatic if the type already implements -//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). -//! So things like [String] will work out of the box. -//! -//! TODO! This is all wrong. Need to talk about VS, not Version. -//! Our Version trait requires -//! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). //! For convenience, this library provides [SemanticVersion] //! that implements semantic versioning rules. //! @@ -56,7 +46,7 @@ //! dependency_provider.add_dependencies("icons", 1u32, []); //! //! // Run the algorithm. -//! let solution = resolve(&dependency_provider, "root", 1u32).unwrap(); +//! let solution = dependency_provider.resolve(&"root", 1u32).unwrap(); //! ``` //! //! # DependencyProvider trait @@ -69,40 +59,58 @@ //! trait for our own type. //! Let's say that we will use [String] for packages, //! and [SemanticVersion] for versions. -//! This may be done quite easily by implementing the three following functions. +//! This may be done quite easily by implementing the following functions. //! ``` -//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, DependencyConstraints, Map}; +//! # use pubgrub::{DependencyProvider, Dependencies, Ranges, DependencyConstraints, Map, PackageId, PackageArena, VersionIndex, VersionSet}; //! # use std::error::Error; +//! # use std::fmt::Display; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; //! # //! # struct MyDependencyProvider; //! # -//! type SemVS = Ranges; -//! //! impl DependencyProvider for MyDependencyProvider { -//! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Infallible> { +//! fn choose_version( +//! &mut self, +//! package_id: PackageId, +//! set: VersionSet, +//! package_store: &PackageArena, +//! ) -> Result, Infallible> { //! unimplemented!() //! } //! //! type Priority = usize; -//! fn prioritize(&self, package: &String, range: &SemVS) -> Self::Priority { +//! fn prioritize( +//! &mut self, +//! package_id: PackageId, +//! set: VersionSet, +//! package_store: &PackageArena, +//! ) -> Self::Priority { //! unimplemented!() //! } //! //! fn get_dependencies( -//! &self, -//! package: &String, -//! version: &SemanticVersion, -//! ) -> Result, Infallible> { +//! &mut self, +//! package_id: PackageId, +//! version_index: VersionIndex, +//! package_store: &mut PackageArena, +//! ) -> Result, Infallible> { //! Ok(Dependencies::Available(DependencyConstraints::default())) //! } //! //! type Err = Infallible; //! type P = String; -//! type V = SemanticVersion; -//! type VS = SemVS; //! type M = String; +//! +//! fn package_version_display<'a>(&'a self, package: &'a Self::P, version_index: VersionIndex) -> impl Display + 'a { +//! let s: String = unimplemented!(); +//! s +//! } +//! +//! fn package_version_set_display<'a>(&'a self, package: &'a Self::P, version_set: VersionSet) -> impl Display + 'a { +//! let s: String = unimplemented!(); +//! s +//! } //! } //! ``` //! @@ -154,16 +162,26 @@ //! Derived incompatibilities are obtained during the algorithm execution by deduction, //! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c". //! -//! This crate defines a [Reporter] trait, with an associated -//! [Output](Reporter::Output) type and a single method. +//! This crate defines the following [Reporter] trait: //! ``` -//! # use pubgrub::{Package, VersionSet, DerivationTree}; +//! # use pubgrub::{DerivationTree, DependencyProvider, NoSolutionError, ReportFormatter}; //! # use std::fmt::{Debug, Display}; //! # -//! pub trait Reporter { +//! pub trait Reporter { +//! /// Output type of the report. //! type Output; //! -//! fn report(derivation_tree: &DerivationTree) -> Self::Output; +//! /// Generate a report from the error +//! /// describing the resolution failure using the default formatter. +//! fn report(error: &NoSolutionError, dependency_provider: &DP) -> Self::Output; +//! +//! /// Generate a report from the error +//! /// describing the resolution failure using a custom formatter. +//! fn report_with_formatter( +//! error: &NoSolutionError, +//! formatter: &impl ReportFormatter, +//! dependency_provider: &DP, +//! ) -> Self::Output; //! } //! ``` //! Implementing a [Reporter] may involve a lot of heuristics @@ -176,15 +194,16 @@ //! # //! # type NumVS = Ranges; //! # -//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); +//! # let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); +//! # dependency_provider.add_dependencies("root", 1u32, []); //! # let root_package = "root"; //! # let root_version = 1u32; //! # -//! match resolve(&dependency_provider, root_package, root_version) { +//! match dependency_provider.resolve(&root_package, root_version) { //! Ok(solution) => println!("{:?}", solution), -//! Err(PubGrubError::NoSolution(mut derivation_tree)) => { -//! derivation_tree.collapse_no_versions(); -//! eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); +//! Err(PubGrubError::NoSolution(mut error)) => { +//! error.derivation_tree.collapse_no_versions(); +//! eprintln!("{}", DefaultStringReporter::report(&error, &dependency_provider)); //! } //! Err(err) => panic!("{:?}", err), //! }; @@ -211,29 +230,32 @@ #![warn(missing_docs)] mod error; +pub mod helpers; mod package; mod provider; mod report; +mod semantic; mod solver; mod term; mod type_aliases; mod version; -mod version_set; pub use error::{NoSolutionError, PubGrubError}; -pub use package::Package; -pub use provider::OfflineDependencyProvider; +pub use package::{PackageArena, PackageId}; +pub use provider::{OfflineDependencyProvider, VersionRanges}; pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, }; +pub use semantic::{SemanticVersion, VersionParseError}; pub use solver::{resolve, Dependencies, DependencyProvider}; pub use term::Term; -pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; -pub use version::{SemanticVersion, VersionParseError}; +pub use type_aliases::{ + DependencyConstraints, FxIndexMap, FxIndexSet, Map, SelectedDependencies, Set, +}; +pub use version::{VersionIndex, VersionSet}; pub use version_ranges::Ranges; #[deprecated(note = "Use `Ranges` instead")] pub use version_ranges::Ranges as Range; -pub use version_set::VersionSet; mod internal; diff --git a/src/package.rs b/src/package.rs index 36c6069e..f0953622 100644 --- a/src/package.rs +++ b/src/package.rs @@ -1,17 +1,48 @@ -// SPDX-License-Identifier: MPL-2.0 +use std::fmt::Debug; +use std::hash::Hash; -//! Trait for identifying packages. -//! Automatically implemented for traits implementing -//! [Clone] + [Eq] + [Hash] + [Debug] + [Display]. +use crate::FxIndexSet; -use std::fmt::{Debug, Display}; -use std::hash::Hash; +/// Type for identifying packages. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct PackageId(pub(crate) u32); + +impl PackageId { + /// Get the inner value. + #[inline] + pub fn get(self) -> u32 { + self.0 + } +} + +/// Package arena +#[derive(Debug, Clone)] +pub struct PackageArena

(FxIndexSet

); + +impl PackageArena

{ + /// Create an empty package arena. + pub(crate) fn new() -> Self { + Self(Default::default()) + } + + /// Iterate over packages. + pub(crate) fn into_iter(self) -> indexmap::set::IntoIter

{ + self.0.into_iter() + } + + /// Get the package identifier of a package. + pub fn get(&self, package: &P) -> Option { + Some(PackageId(self.0.get_index_of(package)? as u32)) + } -/// Trait for identifying packages. -/// Automatically implemented for types already implementing -/// [Clone] + [Eq] + [Hash] + [Debug] + [Display]. -pub trait Package: Clone + Eq + Hash + Debug + Display {} + /// Insert a package in the arena and get its identifier. + pub fn insert(&mut self, package: P) -> PackageId { + PackageId(self.0.insert_full(package).0 as u32) + } -/// Automatically implement the Package trait for any type -/// that already implement [Clone] + [Eq] + [Hash] + [Debug] + [Display]. -impl Package for T {} + /// Get a package from its identifier. + pub fn pkg(&self, package_id: PackageId) -> Option<&P> { + self.0.get_index(package_id.get() as usize) + } +} diff --git a/src/provider.rs b/src/provider.rs index 83268795..552819c3 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,29 +1,129 @@ +use std::borrow::Borrow; use std::cmp::Reverse; -use std::collections::BTreeMap; use std::convert::Infallible; +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::ops::Bound; -use crate::{Dependencies, DependencyConstraints, DependencyProvider, Map, Package, VersionSet}; +use crate::{ + helpers::PackageVersionWrapper, resolve, Dependencies, DependencyProvider, FxIndexSet, Map, + PackageArena, PackageId, PubGrubError, Ranges, VersionIndex, VersionSet, +}; + +/// Version range. +pub trait VersionRanges: Debug + Display { + /// Associated version type. + type V: Debug + Display + Clone + Ord; + + /// Returns true if this version range contains the specified value. + fn contains(&self, version: &Self::V) -> bool; + + /// Returns true if this version range contains the specified values. + fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator + 's + where + I: IntoIterator + 's, + BV: Borrow + 's; + + /// Returns the bounding range of this version range. + #[allow(clippy::type_complexity)] + fn bounding_range(&self) -> Option<(Bound<&Self::V>, Bound<&Self::V>)>; + + /// Returns a version range for the provided ordered versions. + fn from_ordered_versions(versions: impl IntoIterator + Clone) -> Self; +} + +impl VersionRanges for Ranges { + type V = V; + + fn contains(&self, version: &Self::V) -> bool { + self.contains(version) + } + + fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator + 's + where + I: IntoIterator + 's, + BV: Borrow + 's, + { + self.contains_many(versions.into_iter()) + } + + fn bounding_range(&self) -> Option<(Bound<&Self::V>, Bound<&Self::V>)> { + self.bounding_range() + } + + fn from_ordered_versions(versions: impl IntoIterator + Clone) -> Self { + let all_iter = versions.clone().into_iter(); + let versions = versions.into_iter(); + let mut range = Ranges::empty(); + for (v, ok) in versions { + if ok { + range = range.union(&Ranges::singleton(v)); + } + } + range.simplify(all_iter.map(|(v, _)| v)) + } +} /// A basic implementation of [DependencyProvider]. #[derive(Debug, Clone, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - feature = "serde", - serde(bound( - serialize = "VS::V: serde::Serialize, VS: serde::Serialize, P: serde::Serialize", - deserialize = "VS::V: serde::Deserialize<'de>, VS: serde::Deserialize<'de>, P: serde::Deserialize<'de>" - )) -)] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct OfflineDependencyProvider { - dependencies: Map>>, +pub struct OfflineDependencyProvider { + #[allow(clippy::type_complexity)] + dependencies: Map)>>, + conflicts: Map, +} + +#[cfg(feature = "serde")] +impl serde::Serialize + for OfflineDependencyProvider +where + P: serde::Serialize, + R::V: serde::Serialize, + R: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use std::collections::BTreeMap; + + self.dependencies + .iter() + .map(|(p, versions)| (p, versions.iter().map(|(v, dmap)| (v, dmap)).collect())) + .collect::>>>() + .serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de, P: Debug + Display + Clone + Eq + Hash, R: VersionRanges> serde::Deserialize<'de> + for OfflineDependencyProvider +where + P: serde::Deserialize<'de>, + R::V: serde::Deserialize<'de>, + R: serde::Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use std::collections::BTreeMap; + + Ok(Self { + dependencies: >>>::deserialize(deserializer)? + .into_iter() + .map(|(p, versions)| (p, versions.into_iter().collect())) + .collect(), + conflicts: Map::default(), + }) + } } -impl OfflineDependencyProvider { +impl OfflineDependencyProvider { /// Creates an empty OfflineDependencyProvider with no dependencies. pub fn new() -> Self { Self { dependencies: Map::default(), + conflicts: Map::default(), } } @@ -37,20 +137,19 @@ impl OfflineDependencyProvider { /// The API does not allow to add dependencies one at a time to uphold an assumption that /// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies) /// provides all dependencies of a given package (p) and version (v) pair. - pub fn add_dependencies>( + pub fn add_dependencies>( &mut self, package: P, - version: impl Into, + version: impl Into, dependencies: I, ) { - let package_deps = dependencies.into_iter().collect(); - let v = version.into(); - *self - .dependencies - .entry(package) - .or_default() - .entry(v) - .or_default() = package_deps; + let version = version.into(); + let pkg_deps = self.dependencies.entry(package).or_default(); + + match pkg_deps.binary_search_by(|(v, _)| v.cmp(&version)) { + Ok(idx) => pkg_deps[idx].1 = dependencies.into_iter().collect(), + Err(idx) => pkg_deps.insert(idx, (version, dependencies.into_iter().collect())), + } } /// Lists packages that have been saved. @@ -60,14 +159,72 @@ impl OfflineDependencyProvider { /// Lists versions of saved packages in sorted order. /// Returns [None] if no information is available regarding that package. - pub fn versions(&self, package: &P) -> Option> { - self.dependencies.get(package).map(|k| k.keys()) + pub fn versions(&self, p: &P) -> Option + Clone> { + Some(self.dependencies.get(p)?.iter().map(|(v, _)| v)) } /// Lists dependencies of a given package and version. - /// Returns [None] if no information is available regarding that package and version pair. - fn dependencies(&self, package: &P, version: &VS::V) -> Option> { - self.dependencies.get(package)?.get(version).cloned() + pub fn dependencies(&self, p: &P, v: &R::V) -> Option<&Map> { + let pkg_deps = self.dependencies.get(p)?; + pkg_deps + .binary_search_by(|(version, _)| version.cmp(v)) + .map(|idx| &pkg_deps[idx].1) + .ok() + } + + /// Returns the resolve parameters from a root package and version. + pub fn resolve_parameters( + &self, + p: P, + v: impl Into, + ) -> Option<(PackageVersionWrapper

, VersionIndex)> { + let versions = self.dependencies.get(&p)?; + + let v = v.into(); + let true_version_index = self + .dependencies + .get(&p)? + .iter() + .enumerate() + .find(|&(_, (pv, _))| pv == &v) + .map(|(i, _)| i as u64)?; + + let (root_pkg, root_version_index) = PackageVersionWrapper::new_pkg( + p, + true_version_index, + (versions.len() as u64).try_into().unwrap(), + ); + + Some((root_pkg, root_version_index)) + } + + /// Finds a set of packages satisfying dependency bounds for a given package + version pair. + pub fn resolve( + &mut self, + p: P, + v: impl Into, + ) -> Result, PubGrubError> { + let (root_pkg, root_version_index) = self + .resolve_parameters(p, v) + .ok_or_else(|| PubGrubError::NoRoot)?; + + let res = resolve(self, root_pkg, root_version_index)?; + + Ok(res + .into_iter() + .filter_map(|(wrapper, version_index)| { + let (pkg, true_version_index) = wrapper.into_inner(version_index)?; + + let version = self + .dependencies + .get(&pkg) + .into_iter() + .flat_map(|versions| versions.iter().map(|(v, _)| v)) + .nth(true_version_index as usize)?; + + Some((pkg, version.clone())) + }) + .collect()) } } @@ -76,45 +233,199 @@ impl OfflineDependencyProvider { /// Currently packages are picked with the fewest versions contained in the constraints first. /// But, that may change in new versions if better heuristics are found. /// Versions are picked with the newest versions first. -impl DependencyProvider for OfflineDependencyProvider { - type P = P; - type V = VS::V; - type VS = VS; - type M = String; +impl DependencyProvider + for OfflineDependencyProvider +{ + type P = PackageVersionWrapper

; + type M = &'static str; type Err = Infallible; #[inline] - fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { - Ok(self - .dependencies - .get(package) - .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) + fn choose_version( + &mut self, + _: PackageId, + set: VersionSet, + _: &PackageArena, + ) -> Result, Infallible> { + Ok(set.last()) } - type Priority = Reverse; + type Priority = Reverse; #[inline] - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { - Reverse( - self.dependencies - .get(package) - .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) - .unwrap_or(0), - ) + fn prioritize( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Self::Priority { + let version_count = set.count(); + if version_count == 0 { + return Reverse(0); + } + let pkg = match package_store.pkg(package_id).unwrap() { + PackageVersionWrapper::Pkg(p) => p.pkg(), + PackageVersionWrapper::VirtualPkg(p) => p.pkg(), + PackageVersionWrapper::VirtualDep(p) => p.pkg(), + }; + let conflict_count = self.conflicts.get(pkg).copied().unwrap_or_default(); + + Reverse(((u32::MAX as u64).saturating_sub(conflict_count) << 6) + version_count as u64) } - #[inline] fn get_dependencies( - &self, - package: &P, - version: &VS::V, - ) -> Result, Infallible> { - Ok(match self.dependencies(package, version) { - None => { - Dependencies::Unavailable("its dependencies could not be determined".to_string()) + &mut self, + package_id: PackageId, + version_index: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, Infallible> { + let mut dep_map = Map::default(); + + let wrapper = package_store.pkg(package_id).unwrap(); + let inner = wrapper.inner(version_index).map(|(p, v)| (p.clone(), v)); + + if let Some((d, vs)) = wrapper.dependency(version_index) { + dep_map.insert(package_store.insert(d), vs); + } + + let Some((pkg, true_version_index)) = inner else { + return Ok(Dependencies::Available(dep_map)); + }; + + let msg = "dependencies could not be determined"; + + let Some(pkg_deps) = self.dependencies.get(&pkg) else { + return Ok(Dependencies::Unavailable(msg)); + }; + let Some(deps) = pkg_deps + .iter() + .map(|(_, dmap)| dmap) + .nth(true_version_index as usize) + else { + return Ok(Dependencies::Unavailable(msg)); + }; + + for (dep, r) in deps { + let empty_vec = vec![]; + let versions = self.dependencies.get(dep).unwrap_or(&empty_vec); + let version_count = versions.len() as u64; + let dep = dep.clone(); + + let (d, vs) = match r.bounding_range() { + None => PackageVersionWrapper::new_empty_dep(dep), + Some((Bound::Unbounded, Bound::Unbounded)) => { + PackageVersionWrapper::new_dep(dep, 0..version_count, version_count) + } + Some((Bound::Included(start), Bound::Included(end))) if start == end => { + if let Ok(idx) = versions.binary_search_by(|(v, _)| v.cmp(start)) { + PackageVersionWrapper::new_singleton_dep(dep, idx as u64, version_count) + } else { + PackageVersionWrapper::new_empty_dep(dep) + } + } + Some((start, end)) => { + let start_idx = match start { + Bound::Unbounded => 0, + Bound::Included(start) | Bound::Excluded(start) => { + versions.partition_point(|(v, _)| v < start) + } + }; + let end_idx = match end { + Bound::Unbounded => version_count as usize, + Bound::Included(end) | Bound::Excluded(end) => { + versions.partition_point(|(v, _)| v <= end) + } + }; + + let true_version_indices = r + .contains_many(versions[start_idx..end_idx].iter().map(|(v, _)| v)) + .enumerate() + .filter(|&(_, ok)| ok) + .map(|(i, _)| (start_idx + i) as u64); + + PackageVersionWrapper::new_dep(dep, true_version_indices, version_count) + } + }; + + dep_map.insert(package_store.insert(d), vs); + } + + Ok(Dependencies::Available(dep_map)) + } + + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a { + match package.inner(version_index) { + None => format!("{package} @ {}", version_index.get()), + Some((pkg, true_version_index)) => { + if let Some(version) = self + .dependencies + .get(pkg) + .into_iter() + .flat_map(|versions| versions.iter().map(|(v, _)| v)) + .nth(true_version_index as usize) + { + format!("{pkg} @ {version}") + } else { + format!("{pkg} @ ") + } } - Some(dependencies) => Dependencies::Available(dependencies), - }) + } + } + + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a { + match package.inner_pkg() { + Some(p) => { + let true_version_indices = version_set + .iter() + .map(|v| package.inner(v).unwrap().1 as usize) + .collect::>(); + + let versions = self + .dependencies + .get(p) + .map(|versions| { + R::from_ordered_versions(versions.iter().enumerate().map(|(i, (v, _))| { + let ok = true_version_indices.contains(&i); + (v.clone(), ok) + })) + }) + .unwrap_or_else(|| R::from_ordered_versions([])); + + format!("{p} @ {versions}") + } + _ => { + let version_indices = version_set + .iter() + .map(|version_index| version_index.get()) + .collect::>(); + + format!("{package} @ {version_indices:?}") + } + } + } + + #[inline] + fn register_conflict( + &mut self, + package_ids: impl Iterator, + package_store: &PackageArena, + ) { + for package_id in package_ids { + let pkg = match package_store.pkg(package_id).unwrap() { + PackageVersionWrapper::Pkg(p) => p.pkg(), + PackageVersionWrapper::VirtualPkg(p) => p.pkg(), + PackageVersionWrapper::VirtualDep(p) => p.pkg(), + }; + *self.conflicts.entry(pkg.clone()).or_default() += 1; + } } } diff --git a/src/report.rs b/src/report.rs index af232260..dd4e4f50 100644 --- a/src/report.rs +++ b/src/report.rs @@ -7,54 +7,56 @@ use std::fmt::{self, Debug, Display}; use std::ops::Deref; use std::sync::Arc; -use crate::{Map, Package, Set, Term, VersionSet}; +use crate::{ + DependencyProvider, Map, NoSolutionError, PackageArena, PackageId, Set, Term, VersionIndex, + VersionSet, +}; /// Reporter trait. -pub trait Reporter { +pub trait Reporter { /// Output type of the report. type Output; - /// Generate a report from the derivation tree - /// describing the resolution failure using the default formatter. - fn report(derivation_tree: &DerivationTree) -> Self::Output; + /// Generate a report from the error describing the resolution failure using the default formatter. + fn report(error: &NoSolutionError, dependency_provider: &DP) -> Self::Output; - /// Generate a report from the derivation tree - /// describing the resolution failure using a custom formatter. + /// Generate a report from the error describing the resolution failure using a custom formatter. fn report_with_formatter( - derivation_tree: &DerivationTree, - formatter: &impl ReportFormatter, + error: &NoSolutionError, + formatter: &impl ReportFormatter, + dependency_provider: &DP, ) -> Self::Output; } /// Derivation tree resulting in the impossibility /// to solve the dependencies of our root package. #[derive(Debug, Clone)] -pub enum DerivationTree { +pub enum DerivationTree { /// External incompatibility. - External(External), + External(External), /// Incompatibility derived from two others. - Derived(Derived), + Derived(Derived), } /// Incompatibilities that are not derived from others, /// they have their own reason. #[derive(Debug, Clone)] -pub enum External { +pub enum External { /// Initial incompatibility aiming at picking the root package for the first decision. - NotRoot(P, VS::V), + NotRoot(PackageId, VersionIndex), /// There are no versions in the given set for this package. - NoVersions(P, VS), + NoVersions(PackageId, VersionSet), /// Incompatibility coming from the dependencies of a given package. - FromDependencyOf(P, VS, P, VS), + FromDependencyOf(PackageId, VersionSet, PackageId, VersionSet), /// The package is unusable for reasons outside pubgrub. - Custom(P, VS, M), + Custom(PackageId, VersionSet, M), } /// Incompatibility derived from two others. #[derive(Debug, Clone)] -pub struct Derived { +pub struct Derived { /// Terms of the incompatibility. - pub terms: Map>, + pub terms: Map, /// Indicate if that incompatibility is present multiple times /// in the derivation tree. /// If that is the case, it has a unique id, provided in that option. @@ -62,25 +64,25 @@ pub struct Derived /// and refer to the explanation for the other times. pub shared_id: Option, /// First cause. - pub cause1: Arc>, + pub cause1: Arc>, /// Second cause. - pub cause2: Arc>, + pub cause2: Arc>, } -impl DerivationTree { +impl DerivationTree { /// Get all packages referred to in the derivation tree. - pub fn packages(&self) -> Set<&P> { + pub fn packages(&self) -> Set { let mut packages = Set::default(); match self { - Self::External(external) => match external { - External::FromDependencyOf(p, _, p2, _) => { - packages.insert(p); - packages.insert(p2); + Self::External(external) => match *external { + External::FromDependencyOf(pid1, _, pid2, _) => { + packages.insert(pid1); + packages.insert(pid2); } - External::NoVersions(p, _) - | External::NotRoot(p, _) - | External::Custom(p, _, _) => { - packages.insert(p); + External::NoVersions(pid, _) + | External::NotRoot(pid, _) + | External::Custom(pid, _, _) => { + packages.insert(pid); } }, Self::Derived(derived) => { @@ -94,12 +96,11 @@ impl DerivationTree packages } - /// Merge the [NoVersions](External::NoVersions) external incompatibilities + /// Merge the [`NoVersions`](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. /// This cleans up quite nicely the generated report. - /// You might want to do this if you know that the - /// [DependencyProvider](crate::solver::DependencyProvider) + /// You might want to do this if you know that the [`DependencyProvider`] /// was not run in some kind of offline mode that may not /// have access to all versions existing. pub fn collapse_no_versions(&mut self) { @@ -110,18 +111,18 @@ impl DerivationTree Arc::make_mut(&mut derived.cause1), Arc::make_mut(&mut derived.cause2), ) { - (DerivationTree::External(External::NoVersions(p, r)), ref mut cause2) => { + (DerivationTree::External(External::NoVersions(pid, r)), cause2) => { cause2.collapse_no_versions(); *self = cause2 .clone() - .merge_no_versions(p.to_owned(), r.to_owned()) + .merge_no_versions(pid.to_owned(), r.to_owned()) .unwrap_or_else(|| self.to_owned()); } - (ref mut cause1, DerivationTree::External(External::NoVersions(p, r))) => { + (cause1, DerivationTree::External(External::NoVersions(pid, r))) => { cause1.collapse_no_versions(); *self = cause1 .clone() - .merge_no_versions(p.to_owned(), r.to_owned()) + .merge_no_versions(pid.to_owned(), r.to_owned()) .unwrap_or_else(|| self.to_owned()); } _ => { @@ -133,7 +134,7 @@ impl DerivationTree } } - fn merge_no_versions(self, package: P, set: VS) -> Option { + fn merge_no_versions(self, package_id: PackageId, set: VersionSet) -> Option { match self { // TODO: take care of the Derived case. // Once done, we can remove the Option. @@ -144,20 +145,20 @@ impl DerivationTree // // Cannot be merged because the reason may not match DerivationTree::External(External::NoVersions(_, _)) => None, - DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => { - if p1 == package { + DerivationTree::External(External::FromDependencyOf(pid1, r1, pid2, r2)) => { + if pid1 == package_id { Some(DerivationTree::External(External::FromDependencyOf( - p1, - r1.union(&set), - p2, + pid1, + r1.union(set), + pid2, r2, ))) } else { Some(DerivationTree::External(External::FromDependencyOf( - p1, + pid1, r1, - p2, - r2.union(&set), + pid2, + r2.union(set), ))) } } @@ -167,43 +168,91 @@ impl DerivationTree } } -impl Display for External { +impl External { + /// Returns an object implementing `Display` for this `External`. + pub fn display<'a, DP: DependencyProvider>( + &'a self, + package_store: &'a PackageArena, + dependency_provider: &'a DP, + ) -> ExternalDisplay<'a, DP> { + ExternalDisplay { + external: self, + package_store, + dependency_provider, + } + } +} + +pub struct ExternalDisplay<'a, DP: DependencyProvider> { + external: &'a External, + package_store: &'a PackageArena, + dependency_provider: &'a DP, +} + +impl Display for ExternalDisplay<'_, DP> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::NotRoot(package, version) => { - write!(f, "we are solving dependencies of {} {}", package, version) + match *self.external { + External::NotRoot(package_id, version_index) => { + let p = self.package_store.pkg(package_id).unwrap(); + write!( + f, + "we are solving dependencies of {}", + self.dependency_provider + .package_version_display(p, version_index), + ) } - Self::NoVersions(package, set) => { - if set == &VS::full() { - write!(f, "there is no available version for {}", package) + External::NoVersions(package_id, set) => { + let p = self.package_store.pkg(package_id).unwrap(); + if set == VersionSet::full() { + write!(f, "there is no available version for {p}") } else { - write!(f, "there is no version of {} in {}", package, set) - } - } - Self::Custom(package, set, metadata) => { - if set == &VS::full() { write!( f, - "dependencies of {} are unavailable {}", - package, metadata + "there is no version of {}", + self.dependency_provider.package_version_set_display(p, set), ) + } + } + External::Custom(package_id, set, ref metadata) => { + let p = self.package_store.pkg(package_id).unwrap(); + if set == VersionSet::full() { + write!(f, "dependencies of {p} are unavailable ({metadata})") } else { write!( f, - "dependencies of {} at version {} are unavailable {}", - package, set, metadata + "dependencies of {} are unavailable ({metadata})", + self.dependency_provider.package_version_set_display(p, set), ) } } - Self::FromDependencyOf(p, set_p, dep, set_dep) => { - if set_p == &VS::full() && set_dep == &VS::full() { - write!(f, "{} depends on {}", p, dep) - } else if set_p == &VS::full() { - write!(f, "{} depends on {} {}", p, dep, set_dep) - } else if set_dep == &VS::full() { - write!(f, "{} {} depends on {}", p, set_p, dep) + External::FromDependencyOf(pid, set_p, did, set_dep) => { + let p = self.package_store.pkg(pid).unwrap(); + let d = self.package_store.pkg(did).unwrap(); + if set_p == VersionSet::full() && set_dep == VersionSet::full() { + write!(f, "{p} depends on {d}") + } else if set_p == VersionSet::full() { + write!( + f, + "{p} depends on {}", + self.dependency_provider + .package_version_set_display(d, set_dep), + ) + } else if set_dep == VersionSet::full() { + write!( + f, + "{} depends on {d}", + self.dependency_provider + .package_version_set_display(p, set_p), + ) } else { - write!(f, "{} {} depends on {} {}", p, set_p, dep, set_dep) + write!( + f, + "{} depends on {}", + self.dependency_provider + .package_version_set_display(p, set_p), + self.dependency_provider + .package_version_set_display(d, set_dep), + ) } } } @@ -211,32 +260,47 @@ impl Display for Ex } /// Trait for formatting outputs in the reporter. -pub trait ReportFormatter { +pub trait ReportFormatter { /// Output type of the report. type Output; /// Format an [External] incompatibility. - fn format_external(&self, external: &External) -> Self::Output; + fn format_external( + &self, + external: &External, + package_store: &PackageArena, + dependency_provider: &DP, + ) -> Self::Output; /// Format terms of an incompatibility. - fn format_terms(&self, terms: &Map>) -> Self::Output; + fn format_terms( + &self, + terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, + ) -> Self::Output; /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, - external1: &External, - external2: &External, - current_terms: &Map>, + external1: &External, + external2: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; /// Both causes have already been explained so we use their refs. + #[allow(clippy::too_many_arguments)] fn explain_both_ref( &self, ref_id1: usize, - derived1: &Derived, + derived1: &Derived, ref_id2: usize, - derived2: &Derived, - current_terms: &Map>, + derived2: &Derived, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; /// One cause is derived (already explained so one-line), @@ -245,32 +309,40 @@ pub trait ReportFormatter, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; /// Add an external cause to the chain of explanations. fn and_explain_external( &self, - external: &External, - current_terms: &Map>, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; /// Add an already explained incompat to the chain of explanations. fn and_explain_ref( &self, ref_id: usize, - derived: &Derived, - current_terms: &Map>, + derived: &Derived, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, - prior_external: &External, - external: &External, - current_terms: &Map>, + prior_external: &External, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> Self::Output; } @@ -278,30 +350,77 @@ pub trait ReportFormatter ReportFormatter - for DefaultStringReportFormatter -{ +impl ReportFormatter for DefaultStringReportFormatter { type Output = String; - fn format_external(&self, external: &External) -> String { - external.to_string() + fn format_external( + &self, + external: &External, + package_store: &PackageArena, + dependency_provider: &DP, + ) -> String { + external + .display(package_store, dependency_provider) + .to_string() } - fn format_terms(&self, terms: &Map>) -> Self::Output { - let terms_vec: Vec<_> = terms.iter().collect(); - match terms_vec.as_slice() { + fn format_terms( + &self, + terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, + ) -> Self::Output { + let terms_vec: Vec<_> = terms.iter().map(|(&pid, &t)| (pid, t)).collect(); + match *terms_vec.as_slice() { [] => "version solving failed".into(), // TODO: special case when that unique package is root. - [(package, Term::Positive(range))] => format!("{} {} is forbidden", package, range), - [(package, Term::Negative(range))] => format!("{} {} is mandatory", package, range), - [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => self.format_external( - &External::<_, _, M>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()), - ), - [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => self.format_external( - &External::<_, _, M>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()), - ), - slice => { - let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{} {}", p, t)).collect(); + [(pid, term)] => { + let pkg = package_store.pkg(pid).unwrap(); + if term.is_positive() { + format!( + "{} is forbidden", + dependency_provider.package_version_set_display(pkg, term.version_set()), + ) + } else { + format!( + "{} is mandatory", + dependency_provider.package_version_set_display(pkg, term.version_set()), + ) + } + } + [(pid1, t1), (pid2, t2)] if t1.is_positive() && t2.is_negative() => { + let r1 = t1.version_set(); + let r2 = t2.version_set(); + self.format_external( + &External::FromDependencyOf(pid1, r1, pid2, r2), + package_store, + dependency_provider, + ) + } + [(pid1, t1), (pid2, t2)] if t1.is_negative() && t2.is_positive() => { + let r1 = t1.version_set(); + let r2 = t2.version_set(); + self.format_external( + &External::FromDependencyOf(pid2, r2, pid1, r1), + package_store, + dependency_provider, + ) + } + ref slice => { + let str_terms: Vec<_> = slice + .iter() + .map(|&(pid, t)| { + let pvs = dependency_provider.package_version_set_display( + package_store.pkg(pid).unwrap(), + t.version_set(), + ); + if t.is_positive() { + format!("{pvs}") + } else { + format!("Not ( {pvs} )") + } + }) + .collect(); str_terms.join(", ") + " are incompatible" } } @@ -310,16 +429,23 @@ impl ReportFormatte /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, - external1: &External, - external2: &External, - current_terms: &Map>, + external1: &External, + external2: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} and {}, {}.", - self.format_external(external1), - self.format_external(external2), - ReportFormatter::::format_terms(self, current_terms) + self.format_external(external1, package_store, dependency_provider), + self.format_external(external2, package_store, dependency_provider), + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } @@ -327,19 +453,36 @@ impl ReportFormatte fn explain_both_ref( &self, ref_id1: usize, - derived1: &Derived, + derived1: &Derived, ref_id2: usize, - derived2: &Derived, - current_terms: &Map>, + derived2: &Derived, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {} ({}), {}.", - ReportFormatter::::format_terms(self, &derived1.terms), + ReportFormatter::::format_terms( + self, + &derived1.terms, + package_store, + dependency_provider + ), ref_id1, - ReportFormatter::::format_terms(self, &derived2.terms), + ReportFormatter::::format_terms( + self, + &derived2.terms, + package_store, + dependency_provider + ), ref_id2, - ReportFormatter::::format_terms(self, current_terms) + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } @@ -349,30 +492,49 @@ impl ReportFormatte fn explain_ref_and_external( &self, ref_id: usize, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {}, {}.", - ReportFormatter::::format_terms(self, &derived.terms), + ReportFormatter::::format_terms( + self, + &derived.terms, + package_store, + dependency_provider + ), ref_id, - self.format_external(external), - ReportFormatter::::format_terms(self, current_terms) + self.format_external(external, package_store, dependency_provider), + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } /// Add an external cause to the chain of explanations. fn and_explain_external( &self, - external: &External, - current_terms: &Map>, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { format!( "And because {}, {}.", - self.format_external(external), - ReportFormatter::::format_terms(self, current_terms) + self.format_external(external, package_store, dependency_provider), + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } @@ -380,29 +542,48 @@ impl ReportFormatte fn and_explain_ref( &self, ref_id: usize, - derived: &Derived, - current_terms: &Map>, + derived: &Derived, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { format!( "And because {} ({}), {}.", - ReportFormatter::::format_terms(self, &derived.terms), + ReportFormatter::::format_terms( + self, + &derived.terms, + package_store, + dependency_provider + ), ref_id, - ReportFormatter::::format_terms(self, current_terms) + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, - prior_external: &External, - external: &External, - current_terms: &Map>, + prior_external: &External, + external: &External, + current_terms: &Map, + package_store: &PackageArena, + dependency_provider: &DP, ) -> String { format!( "And because {} and {}, {}.", - self.format_external(prior_external), - self.format_external(external), - ReportFormatter::::format_terms(self, current_terms) + self.format_external(prior_external, package_store, dependency_provider), + self.format_external(external, package_store, dependency_provider), + ReportFormatter::::format_terms( + self, + current_terms, + package_store, + dependency_provider + ) ) } } @@ -428,17 +609,14 @@ impl DefaultStringReporter { } } - fn build_recursive< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( + fn build_recursive>( &mut self, - derived: &Derived, + derived: &Derived, formatter: &F, + package_store: &PackageArena, + dependency_provider: &DP, ) { - self.build_recursive_helper(derived, formatter); + self.build_recursive_helper(derived, formatter, package_store, dependency_provider); if let Some(id) = derived.shared_id { #[allow(clippy::map_entry)] // `add_line_ref` not compatible with proposed fix. if !self.shared_with_ref.contains_key(&id) { @@ -448,15 +626,12 @@ impl DefaultStringReporter { }; } - fn build_recursive_helper< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( + fn build_recursive_helper>( &mut self, - current: &Derived, + current: &Derived, formatter: &F, + package_store: &PackageArena, + dependency_provider: &DP, ) { match (current.cause1.deref(), current.cause2.deref()) { (DerivationTree::External(external1), DerivationTree::External(external2)) => { @@ -465,16 +640,32 @@ impl DefaultStringReporter { external1, external2, ¤t.terms, + package_store, + dependency_provider, )); } (DerivationTree::Derived(derived), DerivationTree::External(external)) => { // One cause is derived, so we explain this first // then we add the one-line external part // and finally conclude with the current incompatibility. - self.report_one_each(derived, external, ¤t.terms, formatter); + self.report_one_each( + derived, + external, + ¤t.terms, + formatter, + package_store, + dependency_provider, + ); } (DerivationTree::External(external), DerivationTree::Derived(derived)) => { - self.report_one_each(derived, external, ¤t.terms, formatter); + self.report_one_each( + derived, + external, + ¤t.terms, + formatter, + package_store, + dependency_provider, + ); } (DerivationTree::Derived(derived1), DerivationTree::Derived(derived2)) => { // This is the most complex case since both causes are also derived. @@ -490,19 +681,41 @@ impl DefaultStringReporter { ref2, derived2, ¤t.terms, + package_store, + dependency_provider, )), // Otherwise, if one only has a line number reference, // we recursively call the one without reference and then // add the one with reference to conclude. (Some(ref1), None) => { - self.build_recursive(derived2, formatter); - self.lines - .push(formatter.and_explain_ref(ref1, derived1, ¤t.terms)); + self.build_recursive( + derived2, + formatter, + package_store, + dependency_provider, + ); + self.lines.push(formatter.and_explain_ref( + ref1, + derived1, + ¤t.terms, + package_store, + dependency_provider, + )); } (None, Some(ref2)) => { - self.build_recursive(derived1, formatter); - self.lines - .push(formatter.and_explain_ref(ref2, derived2, ¤t.terms)); + self.build_recursive( + derived1, + formatter, + package_store, + dependency_provider, + ); + self.lines.push(formatter.and_explain_ref( + ref2, + derived2, + ¤t.terms, + package_store, + dependency_provider, + )); } // Finally, if no line reference exists yet, // we call recursively the first one and then, @@ -512,19 +725,36 @@ impl DefaultStringReporter { // recursively call on the second node, // and finally conclude. (None, None) => { - self.build_recursive(derived1, formatter); + self.build_recursive( + derived1, + formatter, + package_store, + dependency_provider, + ); if derived1.shared_id.is_some() { self.lines.push("".into()); - self.build_recursive(current, formatter); + self.build_recursive( + current, + formatter, + package_store, + dependency_provider, + ); } else { self.add_line_ref(); let ref1 = self.ref_count; self.lines.push("".into()); - self.build_recursive(derived2, formatter); + self.build_recursive( + derived2, + formatter, + package_store, + dependency_provider, + ); self.lines.push(formatter.and_explain_ref( ref1, derived1, ¤t.terms, + package_store, + dependency_provider, )); } } @@ -537,17 +767,14 @@ impl DefaultStringReporter { /// /// The result will depend on the fact that the derived incompatibility /// has already been explained or not. - fn report_one_each< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( + fn report_one_each>( &mut self, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map, formatter: &F, + package_store: &PackageArena, + dependency_provider: &DP, ) { match self.line_ref_of(derived.shared_id) { Some(ref_id) => self.lines.push(formatter.explain_ref_and_external( @@ -555,49 +782,63 @@ impl DefaultStringReporter { derived, external, current_terms, + package_store, + dependency_provider, )), - None => self.report_recurse_one_each(derived, external, current_terms, formatter), + None => self.report_recurse_one_each( + derived, + external, + current_terms, + formatter, + package_store, + dependency_provider, + ), } } /// Report one derived (without a line ref yet) and one external. - fn report_recurse_one_each< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( + fn report_recurse_one_each>( &mut self, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map, formatter: &F, + package_store: &PackageArena, + dependency_provider: &DP, ) { match (derived.cause1.deref(), derived.cause2.deref()) { // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::Derived(prior_derived), DerivationTree::External(prior_external)) => { - self.build_recursive(prior_derived, formatter); + self.build_recursive(prior_derived, formatter, package_store, dependency_provider); self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, + package_store, + dependency_provider, )); } // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::External(prior_external), DerivationTree::Derived(prior_derived)) => { - self.build_recursive(prior_derived, formatter); + self.build_recursive(prior_derived, formatter, package_store, dependency_provider); self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, + package_store, + dependency_provider, )); } _ => { - self.build_recursive(derived, formatter); - self.lines - .push(formatter.and_explain_external(external, current_terms)); + self.build_recursive(derived, formatter, package_store, dependency_provider); + self.lines.push(formatter.and_explain_external( + external, + current_terms, + package_store, + dependency_provider, + )); } } } @@ -617,32 +858,45 @@ impl DefaultStringReporter { } } -impl Reporter - for DefaultStringReporter -{ +impl Reporter for DefaultStringReporter { type Output = String; - fn report(derivation_tree: &DerivationTree) -> Self::Output { + fn report(error: &NoSolutionError, dependency_provider: &DP) -> Self::Output { let formatter = DefaultStringReportFormatter; - match derivation_tree { - DerivationTree::External(external) => formatter.format_external(external), + match &error.derivation_tree { + DerivationTree::External(external) => { + formatter.format_external(external, &error.package_store, dependency_provider) + } DerivationTree::Derived(derived) => { let mut reporter = Self::new(); - reporter.build_recursive(derived, &formatter); + reporter.build_recursive( + derived, + &formatter, + &error.package_store, + dependency_provider, + ); reporter.lines.join("\n") } } } fn report_with_formatter( - derivation_tree: &DerivationTree, - formatter: &impl ReportFormatter, + error: &NoSolutionError, + formatter: &impl ReportFormatter, + dependency_provider: &DP, ) -> Self::Output { - match derivation_tree { - DerivationTree::External(external) => formatter.format_external(external), + match &error.derivation_tree { + DerivationTree::External(external) => { + formatter.format_external(external, &error.package_store, dependency_provider) + } DerivationTree::Derived(derived) => { let mut reporter = Self::new(); - reporter.build_recursive(derived, formatter); + reporter.build_recursive( + derived, + formatter, + &error.package_store, + dependency_provider, + ); reporter.lines.join("\n") } } diff --git a/src/semantic.rs b/src/semantic.rs new file mode 100644 index 00000000..4c952fb2 --- /dev/null +++ b/src/semantic.rs @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Semantic version implementation. + +use std::fmt::{self, Debug, Display}; +use std::str::FromStr; + +use thiserror::Error; + +/// Type for semantic versions: major.minor.patch. +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct SemanticVersion { + major: u32, + minor: u32, + patch: u32, +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SemanticVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{}", self)) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SemanticVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +// Constructors +impl SemanticVersion { + /// Create a version with "major", "minor" and "patch" values. + /// `version = major.minor.patch` + pub fn new(major: u32, minor: u32, patch: u32) -> Self { + Self { + major, + minor, + patch, + } + } + + /// Version 0.0.0. + pub fn zero() -> Self { + Self::new(0, 0, 0) + } + + /// Version 1.0.0. + pub fn one() -> Self { + Self::new(1, 0, 0) + } + + /// Version 2.0.0. + pub fn two() -> Self { + Self::new(2, 0, 0) + } +} + +// Convert a tuple (major, minor, patch) into a version. +impl From<(u32, u32, u32)> for SemanticVersion { + fn from(tuple: (u32, u32, u32)) -> Self { + let (major, minor, patch) = tuple; + Self::new(major, minor, patch) + } +} + +// Convert a &(major, minor, patch) into a version. +impl From<&(u32, u32, u32)> for SemanticVersion { + fn from(tuple: &(u32, u32, u32)) -> Self { + let (major, minor, patch) = *tuple; + Self::new(major, minor, patch) + } +} + +// Convert an &version into a version. +impl From<&SemanticVersion> for SemanticVersion { + fn from(v: &SemanticVersion) -> Self { + *v + } +} + +// Convert a version into a tuple (major, minor, patch). +impl From for (u32, u32, u32) { + fn from(v: SemanticVersion) -> Self { + (v.major, v.minor, v.patch) + } +} + +// Bump versions. +impl SemanticVersion { + /// Bump the patch number of a version. + pub fn bump_patch(self) -> Self { + Self::new(self.major, self.minor, self.patch + 1) + } + + /// Bump the minor number of a version. + pub fn bump_minor(self) -> Self { + Self::new(self.major, self.minor + 1, 0) + } + + /// Bump the major number of a version. + pub fn bump_major(self) -> Self { + Self::new(self.major + 1, 0, 0) + } +} + +/// Error creating [SemanticVersion] from [String]. +#[derive(Error, Debug, PartialEq, Eq)] +pub enum VersionParseError { + /// [SemanticVersion] must contain major, minor, patch versions. + #[error("version {full_version} must contain 3 numbers separated by dot")] + NotThreeParts { + /// [SemanticVersion] that was being parsed. + full_version: String, + }, + /// Wrapper around [ParseIntError](core::num::ParseIntError). + #[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")] + ParseIntError { + /// [SemanticVersion] that was being parsed. + full_version: String, + /// A version part where parsing failed. + version_part: String, + /// A specific error resulted from parsing a part of the version as [u32]. + parse_error: String, + }, +} + +impl FromStr for SemanticVersion { + type Err = VersionParseError; + + fn from_str(s: &str) -> Result { + let parse_u32 = |part: &str| { + part.parse::().map_err(|e| Self::Err::ParseIntError { + full_version: s.to_string(), + version_part: part.to_string(), + parse_error: e.to_string(), + }) + }; + + let mut parts = s.split('.'); + match (parts.next(), parts.next(), parts.next(), parts.next()) { + (Some(major), Some(minor), Some(patch), None) => { + let major = parse_u32(major)?; + let minor = parse_u32(minor)?; + let patch = parse_u32(patch)?; + Ok(Self { + major, + minor, + patch, + }) + } + _ => Err(Self::Err::NotThreeParts { + full_version: s.to_string(), + }), + } + } +} + +#[test] +fn from_str_for_semantic_version() { + let parse = |str: &str| str.parse::(); + assert!(parse( + &SemanticVersion { + major: 0, + minor: 1, + patch: 0 + } + .to_string() + ) + .is_ok()); + assert!(parse("1.2.3").is_ok()); + assert_eq!( + parse("1.abc.3"), + Err(VersionParseError::ParseIntError { + full_version: "1.abc.3".to_owned(), + version_part: "abc".to_owned(), + parse_error: "invalid digit found in string".to_owned(), + }) + ); + assert_eq!( + parse("1.2.-3"), + Err(VersionParseError::ParseIntError { + full_version: "1.2.-3".to_owned(), + version_part: "-3".to_owned(), + parse_error: "invalid digit found in string".to_owned(), + }) + ); + assert_eq!( + parse("1.2.9876543210"), + Err(VersionParseError::ParseIntError { + full_version: "1.2.9876543210".to_owned(), + version_part: "9876543210".to_owned(), + parse_error: "number too large to fit in target type".to_owned(), + }) + ); + assert_eq!( + parse("1.2"), + Err(VersionParseError::NotThreeParts { + full_version: "1.2".to_owned(), + }) + ); + assert_eq!( + parse("1.2.3."), + Err(VersionParseError::NotThreeParts { + full_version: "1.2.3.".to_owned(), + }) + ); +} + +impl Display for SemanticVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} diff --git a/src/solver.rs b/src/solver.rs index a8aff4bf..37ed9271 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -25,12 +25,6 @@ //! version solving failed. //! ``` //! -//! The algorithm is generic and works for any type of dependency system -//! as long as packages (P) and versions (V) implement -//! the [Package] and Version traits. -//! [Package] is strictly equivalent and automatically generated -//! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display]. -//! //! ## API //! //! ``` @@ -40,14 +34,14 @@ //! # type NumVS = Ranges; //! # //! # fn try_main() -> Result<(), PubGrubError>> { -//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); +//! # let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let package = "root"; //! # let version = 1u32; -//! let solution = resolve(&dependency_provider, package, version)?; +//! let solution = dependency_provider.resolve(&package, version)?; //! # Ok(()) //! # } //! # fn main() { -//! # assert!(matches!(try_main(), Err(PubGrubError::NoSolution(_)))); +//! # assert!(matches!(try_main(), Err(PubGrubError::NoRoot))); //! # } //! ``` //! @@ -59,54 +53,64 @@ //! to satisfy the dependencies of that package and version pair. //! If there is no solution, the reason will be provided as clear as possible. -use std::collections::BTreeSet as Set; use std::error::Error; use std::fmt::{Debug, Display}; +use std::hash::Hash; use log::{debug, info}; -use crate::internal::{Id, Incompatibility, State}; -use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, VersionSet}; +use crate::{ + internal::{Incompatibility, State}, + DependencyConstraints, DerivationTree, External, NoSolutionError, PackageArena, PackageId, + PubGrubError, SelectedDependencies, VersionIndex, VersionSet, +}; /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. #[cold] pub fn resolve( - dependency_provider: &DP, + dependency_provider: &mut DP, package: DP::P, - version: impl Into, + version_index: VersionIndex, ) -> Result, PubGrubError> { - let mut state: State = State::init(package.clone(), version.into()); - let mut added_dependencies: Map, Set> = Map::default(); - let mut next = state.root_package; + let mut package_store = PackageArena::new(); + let package_id = package_store.insert(package); + let mut state: State = State::init(package_id, version_index); + let mut added_dependencies = Vec::new(); + let mut next = package_id; loop { dependency_provider .should_cancel() - .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; + .map_err(PubGrubError::ErrorInShouldCancel)?; - info!( - "unit_propagation: {:?} = '{}'", - &next, state.package_store[next] - ); - state.unit_propagation(next)?; + info!("unit_propagation: {}", package_store.pkg(next).unwrap()); + match state.unit_propagation(next, &package_store, dependency_provider) { + Ok(t) => t, + Err(DerivationTree::External(External::NoVersions(PackageId(0), _))) => { + return Err(PubGrubError::NoRoot); + } + Err(derivation_tree) => { + return Err(NoSolutionError { + package_store, + derivation_tree, + } + .into()); + } + }; debug!( "Partial solution after unit propagation: {}", - state.partial_solution.display(&state.package_store) + state.partial_solution.display(&package_store) ); - let Some(highest_priority_pkg) = - state.partial_solution.pick_highest_priority_pkg(|p, r| { - dependency_provider.prioritize(&state.package_store[p], r) + let Some(highest_priority_pkg_id) = + state.partial_solution.pick_highest_priority_pkg(|pid, r| { + dependency_provider.prioritize(pid, r, &package_store) }) else { - return Ok(state - .partial_solution - .extract_solution() - .map(|(p, v)| (state.package_store[p].clone(), v)) - .collect()); + return Ok(state.partial_solution.extract_solution(package_store)); }; - next = highest_priority_pkg; + next = highest_priority_pkg_id; let term_intersection = state .partial_solution @@ -114,75 +118,82 @@ pub fn resolve( .ok_or_else(|| { PubGrubError::Failure("a package was chosen but we don't have a term.".into()) })?; + let decision = dependency_provider - .choose_version( - &state.package_store[next], - term_intersection.unwrap_positive(), - ) + .choose_version(next, term_intersection.unwrap_positive(), &package_store) .map_err(PubGrubError::ErrorChoosingPackageVersion)?; - info!( - "DP chose: {:?} = '{}' @ {:?}", - &next, state.package_store[next], decision - ); - // Pick the next compatible version. let v = match decision { None => { - let inc = Incompatibility::no_versions(next, term_intersection.clone()); + info!( + "DP chose: {} (no versions)", + package_store.pkg(next).unwrap() + ); + let inc = Incompatibility::no_versions(next, term_intersection); state.add_incompatibility(inc); continue; } - Some(x) => x, + Some(v) => { + info!( + "DP chose: {}", + dependency_provider + .package_version_display(package_store.pkg(next).unwrap(), v) + ); + v + } }; - if !term_intersection.contains(&v) { + if !term_intersection.contains(v) { return Err(PubGrubError::Failure( "choose_package_version picked an incompatible version".into(), )); } - let is_new_dependency = added_dependencies - .entry(next) - .or_default() - .insert(v.clone()); + // Check if the package version has already been selected. + let idx = next.get() as usize; + if idx + 1 > added_dependencies.len() { + added_dependencies.resize(idx + 1, VersionSet::empty()); + } + if !added_dependencies[idx].contains(v) { + added_dependencies[idx] = added_dependencies[idx].r#union(VersionSet::singleton(v)); - if is_new_dependency { // Retrieve that package dependencies. - let p = next; + let pid = next; let dependencies = dependency_provider - .get_dependencies(&state.package_store[p], &v) + .get_dependencies(pid, v, &mut package_store) .map_err(|err| PubGrubError::ErrorRetrievingDependencies { - package: state.package_store[p].clone(), - version: v.clone(), + package_version: dependency_provider + .package_version_display(package_store.pkg(pid).unwrap(), v) + .to_string(), source: err, })?; let dependencies = match dependencies { Dependencies::Unavailable(reason) => { - state.add_incompatibility(Incompatibility::custom_version( - p, - v.clone(), - reason, - )); + state.add_incompatibility(Incompatibility::custom_version(pid, v, reason)); continue; } Dependencies::Available(x) => x, }; // Add that package and version if the dependencies are not problematic. - let dep_incompats = - state.add_incompatibility_from_dependencies(p, v.clone(), dependencies); + let dep_incompats = state.add_incompatibility_from_dependencies(pid, v, dependencies); - state - .partial_solution - .add_version(p, v, dep_incompats, &state.incompatibility_store); + state.partial_solution.add_version( + pid, + v, + dep_incompats, + &state.incompatibility_store, + &package_store, + dependency_provider, + ); } else { // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // terms and can add the decision directly. info!( - "add_decision (not first time): {:?} = '{}' @ {}", - &next, state.package_store[next], v + "add_decision (not first time): {}", + dependency_provider.package_version_display(package_store.pkg(next).unwrap(), v) ); state.partial_solution.add_decision(next, v); } @@ -190,31 +201,19 @@ pub fn resolve( } /// An enum used by [DependencyProvider] that holds information about package dependencies. -/// For each [Package] there is a set of versions allowed as a dependency. #[derive(Clone)] -pub enum Dependencies { +pub enum Dependencies { /// Package dependencies are unavailable with the reason why they are missing. Unavailable(M), /// Container for all available package versions. - Available(DependencyConstraints), + Available(DependencyConstraints), } /// Trait that allows the algorithm to retrieve available packages and their dependencies. /// An implementor needs to be supplied to the [resolve] function. pub trait DependencyProvider { /// How this provider stores the name of the packages. - type P: Package; - - /// How this provider stores the versions of the packages. - /// - /// A common choice is [`SemanticVersion`][crate::version::SemanticVersion]. - type V: Debug + Display + Clone + Ord; - - /// How this provider stores the version requirements for the packages. - /// The requirements must be able to process the same kind of version as this dependency provider. - /// - /// A common choice is [`Ranges`][version_ranges::Ranges]. - type VS: VersionSet; + type P: Debug + Display + Eq + Hash; /// Type for custom incompatibilities. /// @@ -254,7 +253,12 @@ pub trait DependencyProvider { /// /// Note: the resolver may call this even when the range has not changed, /// if it is more efficient for the resolvers internal data structures. - fn prioritize(&self, package: &Self::P, range: &Self::VS) -> Self::Priority; + fn prioritize( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Self::Priority; /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// @@ -271,26 +275,51 @@ pub trait DependencyProvider { /// packages, it needs to know what version of that package to use. The most common pattern /// is to select the largest version that the range contains. fn choose_version( - &self, - package: &Self::P, - range: &Self::VS, - ) -> Result, Self::Err>; + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Result, Self::Err>; /// Retrieves the package dependencies. /// Return [Dependencies::Unavailable] if its dependencies are unavailable. - #[allow(clippy::type_complexity)] fn get_dependencies( - &self, - package: &Self::P, - version: &Self::V, - ) -> Result, Self::Err>; + &mut self, + package_id: PackageId, + version_index: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, Self::Err>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. /// This is helpful if you want to add some form of early termination like a timeout, /// or you want to add some form of user feedback if things are taking a while. /// If not provided the resolver will run as long as needed. - fn should_cancel(&self) -> Result<(), Self::Err> { + fn should_cancel(&mut self) -> Result<(), Self::Err> { Ok(()) } + + /// Get a representation of a package version. + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a; + + /// Get a representation of a package version set. + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a; + + /// Register a conflict for the given packages. + fn register_conflict( + &mut self, + package_ids: impl Iterator, + package_store: &PackageArena, + ) { + let _ = package_ids; + let _ = package_store; + } } diff --git a/src/term.rs b/src/term.rs index 38faf087..6813aa0c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -5,147 +5,160 @@ use std::fmt::{self, Display}; -use crate::VersionSet; +use crate::{VersionIndex, VersionSet}; /// A positive or negative expression regarding a set of versions. /// -/// `Positive(r)` and `Negative(r.complement())` are not equivalent: -/// * the term `Positive(r)` is satisfied if the package is selected AND the selected version is in `r`. -/// * the term `Negative(r.complement())` is satisfied if the package is not selected OR the selected version is in `r`. +/// `Term::positive(vs)` and `Term::negative(vs.complement())` are not equivalent: +/// * `Term::positive(vs)` is satisfied if the package is selected AND the selected version is in `vs`. +/// * `Term::negative(vs.complement())` is satisfied if the package is not selected OR the selected version is in `vs`. /// -/// A `Positive` term in the partial solution requires a version to be selected, but a `Negative` term +/// A positive term in the partial solution requires a version to be selected, but a negative term /// allows for a solution that does not have that package selected. -/// Specifically, `Positive(VS::empty())` means that there was a conflict (we need to select a version for the package -/// but can't pick any), while `Negative(VS::full())` would mean it is fine as long as we don't select the package. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Term { - /// For example, `1.0.0 <= v < 2.0.0` is a positive expression - /// that is evaluated true if a version is selected - /// and comprised between version 1.0.0 and version 2.0.0. - Positive(VS), - /// The term `not (v < 3.0.0)` is a negative expression - /// that is evaluated true if a version >= 3.0.0 is selected +/// Specifically, `Term::positive(VersionSet::empty())` means that there was a conflict +/// (we need to select a version for the package but can't pick any), +/// while `Term::negative(VersionSet::full())` would mean it is fine as long as we don't select the package. +/// +/// Like [`VersionSet`], this is implemented as a `u64` bitset, +/// where the first bit is set if the unselected package is allowed (for a negative term), +/// and the others bits are set if the package selected at the corresponding version index is allowed. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct Term(u64); + +impl Term { + /// Construct a positive `Term`. + /// For example, `[1, 2]` is a positive expression + /// that is evaluated true if a version with index 1 or 2 is selected. + #[inline] + pub(crate) fn positive(vs: VersionSet) -> Self { + Self(vs.0) + } + + /// Construct a negative `Term`. + /// For example, `not [3, 5]` is a negative expression + /// that is evaluated true if a version with index 3 or 5 is selected /// or if no version is selected at all. - Negative(VS), -} + #[inline] + pub(crate) fn negative(vs: VersionSet) -> Self { + Self(!vs.0) + } -/// Base methods. -impl Term { /// A term that is always true. + #[inline] pub(crate) fn any() -> Self { - Self::Negative(VS::empty()) + Self(!0) } /// A term that is never true. + #[inline] pub(crate) fn empty() -> Self { - Self::Positive(VS::empty()) + Self(0) } /// A positive term containing exactly that version. - pub(crate) fn exact(version: VS::V) -> Self { - Self::Positive(VS::singleton(version)) + #[inline] + pub(crate) fn exact(version_index: VersionIndex) -> Self { + Self::positive(VersionSet::singleton(version_index)) } /// Simply check if a term is positive. - pub(crate) fn is_positive(&self) -> bool { - match self { - Self::Positive(_) => true, - Self::Negative(_) => false, - } + #[inline] + pub fn is_positive(self) -> bool { + self.0 & 1 == 0 + } + + /// Simply check if a term is negative. + #[inline] + pub fn is_negative(self) -> bool { + self.0 & 1 != 0 } /// Negate a term. /// Evaluation of a negated term always returns /// the opposite of the evaluation of the original one. - pub(crate) fn negate(&self) -> Self { - match self { - Self::Positive(set) => Self::Negative(set.clone()), - Self::Negative(set) => Self::Positive(set.clone()), + #[inline] + pub(crate) fn negate(self) -> Self { + Self(!self.0) + } + + /// Get the inner version set. + #[inline] + pub fn version_set(self) -> VersionSet { + if self.is_positive() { + VersionSet(self.0) + } else { + VersionSet(!self.0) } } /// Evaluate a term regarding a given choice of version. - pub(crate) fn contains(&self, v: &VS::V) -> bool { - match self { - Self::Positive(set) => set.contains(v), - Self::Negative(set) => !set.contains(v), - } + #[inline] + pub(crate) fn contains(self, v: VersionIndex) -> bool { + self.0 & VersionSet::singleton(v).0 != 0 } /// Unwrap the set contained in a positive term. /// Will panic if used on a negative set. - pub(crate) fn unwrap_positive(&self) -> &VS { - match self { - Self::Positive(set) => set, - _ => panic!("Negative term cannot unwrap positive set"), + #[inline] + pub(crate) fn unwrap_positive(self) -> VersionSet { + if self.is_positive() { + VersionSet(self.0) + } else { + panic!("Negative term cannot unwrap positive set") } } /// Unwrap the set contained in a negative term. /// Will panic if used on a positive set. - pub(crate) fn unwrap_negative(&self) -> &VS { - match self { - Self::Negative(set) => set, - _ => panic!("Positive term cannot unwrap negative set"), + #[inline] + pub(crate) fn unwrap_negative(self) -> VersionSet { + if self.is_negative() { + VersionSet(!self.0) + } else { + panic!("Positive term cannot unwrap negative set") } } -} -/// Set operations with terms. -impl Term { /// Compute the intersection of two terms. - /// /// The intersection is negative (unselected package is allowed) /// if all terms are negative. - pub(crate) fn intersection(&self, other: &Self) -> Self { - match (self, other) { - (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)), - (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { - Self::Positive(n.complement().intersection(p)) - } - (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)), - } - } - - /// Check whether two terms are mutually exclusive. - /// - /// An optimization for the native implementation of checking whether the intersection of two sets is empty. - pub(crate) fn is_disjoint(&self, other: &Self) -> bool { - match (self, other) { - (Self::Positive(r1), Self::Positive(r2)) => r1.is_disjoint(r2), - // Unselected package is allowed in both terms, so they are never disjoint. - (Self::Negative(_), Self::Negative(_)) => false, - // If the positive term is a subset of the negative term, it lies fully in the region that the negative - // term excludes. - (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { - p.subset_of(n) - } - } + #[inline] + pub(crate) fn intersection(self, other: Self) -> Self { + Self(self.0 & other.0) } /// Compute the union of two terms. /// If at least one term is negative, the union is also negative (unselected package is allowed). - pub(crate) fn union(&self, other: &Self) -> Self { - match (self, other) { - (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.union(r2)), - (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { - Self::Negative(p.complement().intersection(n)) - } - (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.intersection(r2)), - } + #[inline] + pub(crate) fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } + + /// Check whether two terms are mutually exclusive. + #[inline] + pub(crate) fn is_disjoint(self, other: Self) -> bool { + self.0 & other.0 == 0 } /// Indicate if this term is a subset of another term. /// Just like for sets, we say that t1 is a subset of t2 /// if and only if t1 ∩ t2 = t1. - pub(crate) fn subset_of(&self, other: &Self) -> bool { - match (self, other) { - (Self::Positive(r1), Self::Positive(r2)) => r1.subset_of(r2), - (Self::Positive(r1), Self::Negative(r2)) => r1.is_disjoint(r2), - // Only a negative term allows the unselected package, - // so it can never be a subset of a positive term. - (Self::Negative(_), Self::Positive(_)) => false, - (Self::Negative(r1), Self::Negative(r2)) => r2.subset_of(r1), + #[inline] + pub(crate) fn subset_of(self, other: Self) -> bool { + self.0 & other.0 == self.0 + } + + /// Check if a set of terms satisfies or contradicts a given term. + /// Otherwise the relation is inconclusive. + #[inline] + pub(crate) fn relation_with(self, other_terms_intersection: Self) -> Relation { + if other_terms_intersection.subset_of(self) { + Relation::Satisfied + } else if other_terms_intersection.is_disjoint(self) { + Relation::Contradicted + } else { + Relation::Inconclusive } } } @@ -165,89 +178,71 @@ pub(crate) enum Relation { Inconclusive, } -/// Relation between terms. -impl Term { - /// Check if a set of terms satisfies this term. - /// - /// We say that a set of terms S "satisfies" a term t - /// if t must be true whenever every term in S is true. - /// - /// It turns out that this can also be expressed with set operations: - /// S satisfies t if and only if ⋂ S ⊆ t - #[cfg(test)] - fn satisfied_by(&self, terms_intersection: &Self) -> bool { - terms_intersection.subset_of(self) - } - - /// Check if a set of terms contradicts this term. - /// - /// We say that a set of terms S "contradicts" a term t - /// if t must be false whenever every term in S is true. - /// - /// It turns out that this can also be expressed with set operations: - /// S contradicts t if and only if ⋂ S is disjoint with t - /// S contradicts t if and only if (⋂ S) ⋂ t = ∅ - #[cfg(test)] - fn contradicted_by(&self, terms_intersection: &Self) -> bool { - terms_intersection.intersection(self) == Self::empty() - } - - /// Check if a set of terms satisfies or contradicts a given term. - /// Otherwise the relation is inconclusive. - pub(crate) fn relation_with(&self, other_terms_intersection: &Self) -> Relation { - if other_terms_intersection.subset_of(self) { - Relation::Satisfied - } else if self.is_disjoint(other_terms_intersection) { - Relation::Contradicted - } else { - Relation::Inconclusive +impl Display for Term { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_negative() { + write!(f, "Not ( ")?; } - } -} -impl AsRef for Term { - fn as_ref(&self) -> &Self { - self - } -} - -// REPORT ###################################################################### + let mut list = f.debug_list(); + for v in self.version_set().iter() { + list.entry(&v.get()); + } + list.finish()?; -impl Display for Term { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Positive(set) => write!(f, "{}", set), - Self::Negative(set) => write!(f, "Not ( {} )", set), + if self.is_negative() { + write!(f, " )")?; } + + Ok(()) } } -// TESTS ####################################################################### - #[cfg(test)] pub mod tests { - use super::*; use proptest::prelude::*; - use version_ranges::Ranges; - pub fn strategy() -> impl Strategy>> { - prop_oneof![ - version_ranges::proptest_strategy().prop_map(Term::Negative), - version_ranges::proptest_strategy().prop_map(Term::Positive), - ] + use super::*; + + impl Term { + /// Check if a set of terms satisfies this term. + /// + /// We say that a set of terms S "satisfies" a term t + /// if t must be true whenever every term in S is true. + /// + /// It turns out that this can also be expressed with set operations: + /// S satisfies t if and only if ⋂ S ⊆ t + fn satisfied_by(self, terms_intersection: Self) -> bool { + terms_intersection.subset_of(self) + } + + /// Check if a set of terms contradicts this term. + /// + /// We say that a set of terms S "contradicts" a term t + /// if t must be false whenever every term in S is true. + /// + /// It turns out that this can also be expressed with set operations: + /// S contradicts t if and only if ⋂ S is disjoint with t + /// S contradicts t if and only if (⋂ S) ⋂ t = ∅ + fn contradicted_by(self, terms_intersection: Self) -> bool { + terms_intersection.intersection(self) == Self::empty() + } } - proptest! { - // Testing relation -------------------------------- + pub fn strategy() -> impl Strategy { + any::().prop_map(Term) + } + proptest! { + /// Testing relation #[test] fn relation_with(term1 in strategy(), term2 in strategy()) { - match term1.relation_with(&term2) { - Relation::Satisfied => assert!(term1.satisfied_by(&term2)), - Relation::Contradicted => assert!(term1.contradicted_by(&term2)), + match term1.relation_with(term2) { + Relation::Satisfied => assert!(term1.satisfied_by(term2)), + Relation::Contradicted => assert!(term1.contradicted_by(term2)), Relation::Inconclusive => { - assert!(!term1.satisfied_by(&term2)); - assert!(!term1.contradicted_by(&term2)); + assert!(!term1.satisfied_by(term2)); + assert!(!term1.contradicted_by(term2)); } } } @@ -256,30 +251,30 @@ pub mod tests { #[test] fn positive_negative(term1 in strategy(), term2 in strategy()) { let intersection_positive = term1.is_positive() || term2.is_positive(); - let union_positive = term1.is_positive() && term2.is_positive(); - assert_eq!(term1.intersection(&term2).is_positive(), intersection_positive); - assert_eq!(term1.union(&term2).is_positive(), union_positive); + let union_positive = term1.is_positive() & term2.is_positive(); + assert_eq!(term1.intersection(term2).is_positive(), intersection_positive); + assert_eq!(term1.union(term2).is_positive(), union_positive); } #[test] fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) { - let disjoint_def = r1.intersection(&r2) == Term::empty(); - assert_eq!(r1.is_disjoint(&r2), disjoint_def); + let disjoint_def = r1.intersection(r2) == Term::empty(); + assert_eq!(r1.is_disjoint(r2), disjoint_def); } #[test] fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) { - let disjoint_def = r1.intersection(&r2) == r1; - assert_eq!(r1.subset_of(&r2), disjoint_def); + let disjoint_def = r1.intersection(r2) == r1; + assert_eq!(r1.subset_of(r2), disjoint_def); } #[test] fn union_through_intersection(r1 in strategy(), r2 in strategy()) { let union_def = r1 .negate() - .intersection(&r2.negate()) + .intersection(r2.negate()) .negate(); - assert_eq!(r1.union(&r2), union_def); + assert_eq!(r1.union(r2), union_def); } } } diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 6bbd9dd0..8fab6667 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -2,7 +2,7 @@ //! Publicly exported type aliases. -use crate::DependencyProvider; +use crate::{DependencyProvider, PackageId, VersionIndex, VersionSet}; /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; @@ -10,14 +10,19 @@ pub type Map = rustc_hash::FxHashMap; /// Set implementation used by the library. pub type Set = rustc_hash::FxHashSet; +/// IndexMap implementation used by the library. +pub type FxIndexMap = indexmap::IndexMap; + +/// IndexSet implementation used by the library. +pub type FxIndexSet = indexmap::IndexSet; + /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) /// from [DependencyConstraints]. -pub type SelectedDependencies = - Map<::P, ::V>; +pub type SelectedDependencies = Map<::P, VersionIndex>; /// Holds information about all possible versions a given package can accept. /// There is a difference in semantics between an empty map /// inside [DependencyConstraints] and [Dependencies::Unavailable](crate::solver::Dependencies::Unavailable): /// the former means the package has no dependency and it is a known fact, /// while the latter means they could not be fetched by the [DependencyProvider]. -pub type DependencyConstraints = Map; +pub type DependencyConstraints = Map; diff --git a/src/version.rs b/src/version.rs index 445f5d7e..c76d6c73 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,222 +1,128 @@ // SPDX-License-Identifier: MPL-2.0 -//! Traits and implementations to create and compare versions. - -use std::fmt::{self, Debug, Display}; -use std::str::FromStr; - -use thiserror::Error; - -/// Type for semantic versions: major.minor.patch. -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct SemanticVersion { - major: u32, - minor: u32, - patch: u32, -} - -#[cfg(feature = "serde")] -impl serde::Serialize for SemanticVersion { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&format!("{}", self)) +/// Type for identifying a version index. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct VersionIndex(u8); + +impl VersionIndex { + /// Maximum possible version index. + pub const MAX: u64 = (u64::BITS - 1) as u64; + + /// Constructor for a version index. + #[inline] + pub fn new(v: u8) -> Option { + if v < Self::MAX as u8 { + Some(Self(v)) + } else { + None + } } -} -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for SemanticVersion { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - FromStr::from_str(&s).map_err(serde::de::Error::custom) + /// Get the inner version index. + #[inline] + pub fn get(self) -> u8 { + self.0 } } -// Constructors -impl SemanticVersion { - /// Create a version with "major", "minor" and "patch" values. - /// `version = major.minor.patch` - pub fn new(major: u32, minor: u32, patch: u32) -> Self { - Self { - major, - minor, - patch, - } +/// Type for identifying a set of version indices. +/// +/// This is implemented as a `u64` bitset which can represent up to 63 versions. +/// The first bit is kept unset to leave space for the positive/negative bit of [`Term`](super::Term). +/// +/// See the [helpers](super::helpers) module to support more than 63 versions by using a wrapper package. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct VersionSet(pub(crate) u64); + +impl VersionSet { + /// Constructor for an empty set containing no version index. + #[inline] + pub fn empty() -> Self { + Self(0) } - /// Version 0.0.0. - pub fn zero() -> Self { - Self::new(0, 0, 0) + /// Constructor for the set containing all version indices. + #[inline] + pub fn full() -> Self { + Self(u64::MAX & (!1)) } - /// Version 1.0.0. - pub fn one() -> Self { - Self::new(1, 0, 0) + /// Constructor for a set containing exactly one version index. + #[inline] + pub fn singleton(v: VersionIndex) -> Self { + Self(2 << v.0) } - /// Version 2.0.0. - pub fn two() -> Self { - Self::new(2, 0, 0) + /// Compute the complement of this set. + #[inline] + pub fn complement(self) -> Self { + Self((!self.0) & (!1)) } -} -// Convert a tuple (major, minor, patch) into a version. -impl From<(u32, u32, u32)> for SemanticVersion { - fn from(tuple: (u32, u32, u32)) -> Self { - let (major, minor, patch) = tuple; - Self::new(major, minor, patch) + /// Compute the intersection with another set. + #[inline] + pub fn intersection(self, other: Self) -> Self { + Self(self.0 & other.0) } -} -// Convert a &(major, minor, patch) into a version. -impl From<&(u32, u32, u32)> for SemanticVersion { - fn from(tuple: &(u32, u32, u32)) -> Self { - let (major, minor, patch) = *tuple; - Self::new(major, minor, patch) + /// Compute the union with another set. + #[inline] + pub fn union(self, other: Self) -> Self { + Self(self.0 | other.0) } -} -// Convert an &version into a version. -impl From<&SemanticVersion> for SemanticVersion { - fn from(v: &SemanticVersion) -> Self { - *v + /// Evaluate membership of a version index in this set. + #[inline] + pub fn contains(self, v: VersionIndex) -> bool { + self.intersection(Self::singleton(v)) != Self::empty() } -} -// Convert a version into a tuple (major, minor, patch). -impl From for (u32, u32, u32) { - fn from(v: SemanticVersion) -> Self { - (v.major, v.minor, v.patch) + /// Whether the set has no overlapping version indices. + #[inline] + pub fn is_disjoint(self, other: Self) -> bool { + self.intersection(other) == Self::empty() } -} -// Bump versions. -impl SemanticVersion { - /// Bump the patch number of a version. - pub fn bump_patch(self) -> Self { - Self::new(self.major, self.minor, self.patch + 1) + /// Whether all version indices of `self` are contained in `other`. + #[inline] + pub fn subset_of(self, other: Self) -> bool { + self == self.intersection(other) } - /// Bump the minor number of a version. - pub fn bump_minor(self) -> Self { - Self::new(self.major, self.minor + 1, 0) + /// Get an iterator over the version indices contained in the set. + #[inline] + pub fn iter(self) -> impl Iterator { + (0..VersionIndex::MAX) + .filter(move |v| self.0 & (2 << v) != 0) + .map(|v| VersionIndex(v as u8)) } - /// Bump the major number of a version. - pub fn bump_major(self) -> Self { - Self::new(self.major + 1, 0, 0) - } -} - -/// Error creating [SemanticVersion] from [String]. -#[derive(Error, Debug, PartialEq, Eq)] -pub enum VersionParseError { - /// [SemanticVersion] must contain major, minor, patch versions. - #[error("version {full_version} must contain 3 numbers separated by dot")] - NotThreeParts { - /// [SemanticVersion] that was being parsed. - full_version: String, - }, - /// Wrapper around [ParseIntError](core::num::ParseIntError). - #[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")] - ParseIntError { - /// [SemanticVersion] that was being parsed. - full_version: String, - /// A version part where parsing failed. - version_part: String, - /// A specific error resulted from parsing a part of the version as [u32]. - parse_error: String, - }, -} - -impl FromStr for SemanticVersion { - type Err = VersionParseError; - - fn from_str(s: &str) -> Result { - let parse_u32 = |part: &str| { - part.parse::().map_err(|e| Self::Err::ParseIntError { - full_version: s.to_string(), - version_part: part.to_string(), - parse_error: e.to_string(), - }) - }; - - let mut parts = s.split('.'); - match (parts.next(), parts.next(), parts.next(), parts.next()) { - (Some(major), Some(minor), Some(patch), None) => { - let major = parse_u32(major)?; - let minor = parse_u32(minor)?; - let patch = parse_u32(patch)?; - Ok(Self { - major, - minor, - patch, - }) - } - _ => Err(Self::Err::NotThreeParts { - full_version: s.to_string(), - }), + /// Get the first version index of the set. + #[inline] + pub fn first(self) -> Option { + if self != Self::empty() { + Some(VersionIndex((self.0 >> 1).trailing_zeros() as u8)) + } else { + None } } -} -#[test] -fn from_str_for_semantic_version() { - let parse = |str: &str| str.parse::(); - assert!(parse( - &SemanticVersion { - major: 0, - minor: 1, - patch: 0 + /// Get the last version index of the set. + #[inline] + pub fn last(self) -> Option { + if self != Self::empty() { + let v = VersionIndex::MAX - (self.0 >> 1).leading_zeros() as u64; + Some(VersionIndex(v as u8)) + } else { + None } - .to_string() - ) - .is_ok()); - assert!(parse("1.2.3").is_ok()); - assert_eq!( - parse("1.abc.3"), - Err(VersionParseError::ParseIntError { - full_version: "1.abc.3".to_owned(), - version_part: "abc".to_owned(), - parse_error: "invalid digit found in string".to_owned(), - }) - ); - assert_eq!( - parse("1.2.-3"), - Err(VersionParseError::ParseIntError { - full_version: "1.2.-3".to_owned(), - version_part: "-3".to_owned(), - parse_error: "invalid digit found in string".to_owned(), - }) - ); - assert_eq!( - parse("1.2.9876543210"), - Err(VersionParseError::ParseIntError { - full_version: "1.2.9876543210".to_owned(), - version_part: "9876543210".to_owned(), - parse_error: "number too large to fit in target type".to_owned(), - }) - ); - assert_eq!( - parse("1.2"), - Err(VersionParseError::NotThreeParts { - full_version: "1.2".to_owned(), - }) - ); - assert_eq!( - parse("1.2.3."), - Err(VersionParseError::NotThreeParts { - full_version: "1.2.3.".to_owned(), - }) - ); -} + } -impl Display for SemanticVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + /// Count the number of version indices contained in the set. + #[inline] + pub fn count(self) -> usize { + self.0.count_ones() as usize } } diff --git a/src/version_set.rs b/src/version_set.rs deleted file mode 100644 index bf6af37c..00000000 --- a/src/version_set.rs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! As its name suggests, the [VersionSet] trait describes sets of versions. -//! -//! One needs to define -//! - the associate type for versions, -//! - two constructors for the empty set and a singleton set, -//! - the complement and intersection set operations, -//! - and a function to evaluate membership of versions. -//! -//! Two functions are automatically derived, thanks to the mathematical properties of sets. -//! You can overwrite those implementations, but we highly recommend that you don't, -//! except if you are confident in a correct implementation that brings much performance gains. -//! -//! It is also extremely important that the `Eq` trait is correctly implemented. -//! In particular, you can only use `#[derive(Eq, PartialEq)]` if `Eq` is strictly equivalent to the -//! structural equality, i.e. if version sets have canonical representations. -//! Such problems may arise if your implementations of `complement()` and `intersection()` do not -//! return canonical representations so be careful there. - -use std::fmt::{Debug, Display}; - -use crate::Ranges; - -/// Trait describing sets of versions. -pub trait VersionSet: Debug + Display + Clone + Eq { - /// Version type associated with the sets manipulated. - type V: Debug + Display + Clone + Ord; - - // Constructors - /// Constructor for an empty set containing no version. - fn empty() -> Self; - /// Constructor for a set containing exactly one version. - fn singleton(v: Self::V) -> Self; - - // Operations - /// Compute the complement of this set. - fn complement(&self) -> Self; - /// Compute the intersection with another set. - fn intersection(&self, other: &Self) -> Self; - - // Membership - /// Evaluate membership of a version in this set. - fn contains(&self, v: &Self::V) -> bool; - - // Automatically implemented functions ########################### - - /// Constructor for the set containing all versions. - /// Automatically implemented as `Self::empty().complement()`. - fn full() -> Self { - Self::empty().complement() - } - - /// Compute the union with another set. - /// Thanks to set properties, this is automatically implemented as: - /// `self.complement().intersection(&other.complement()).complement()` - fn union(&self, other: &Self) -> Self { - self.complement() - .intersection(&other.complement()) - .complement() - } - - /// Whether the range have no overlapping segments. - fn is_disjoint(&self, other: &Self) -> bool { - self.intersection(other) == Self::empty() - } - - /// Whether all range of `self` are contained in `other`. - fn subset_of(&self, other: &Self) -> bool { - self == &self.intersection(other) - } -} - -impl VersionSet for Ranges { - type V = T; - - fn empty() -> Self { - Ranges::empty() - } - - fn singleton(v: Self::V) -> Self { - Ranges::singleton(v) - } - - fn complement(&self) -> Self { - Ranges::complement(self) - } - - fn intersection(&self, other: &Self) -> Self { - Ranges::intersection(self, other) - } - - fn contains(&self, v: &Self::V) -> bool { - Ranges::contains(self, v) - } - - fn full() -> Self { - Ranges::full() - } - - fn union(&self, other: &Self) -> Self { - Ranges::union(self, other) - } - - fn is_disjoint(&self, other: &Self) -> bool { - Ranges::is_disjoint(self, other) - } - - fn subset_of(&self, other: &Self) -> bool { - Ranges::subset_of(self, other) - } -} diff --git a/tests/examples.rs b/tests/examples.rs index fcc237c1..051608e1 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,17 +1,16 @@ // SPDX-License-Identifier: MPL-2.0 +use std::io::Write; + +use log::LevelFilter; use pubgrub::{ - resolve, DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Ranges, - Reporter as _, SemanticVersion, Set, + DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Ranges, Reporter as _, + SemanticVersion, Set, }; type NumVS = Ranges; type SemVS = Ranges; -use std::io::Write; - -use log::LevelFilter; - fn init_log() { let _ = env_logger::builder() .filter_level(LevelFilter::Trace) @@ -39,7 +38,7 @@ fn no_conflict() { dependency_provider.add_dependencies("bar", (2, 0, 0), []); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); + let computed_solution = dependency_provider.resolve("root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); @@ -75,7 +74,7 @@ fn avoiding_conflict_during_decision_making() { dependency_provider.add_dependencies("bar", (2, 0, 0), []); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); + let computed_solution = dependency_provider.resolve("root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); @@ -110,7 +109,7 @@ fn conflict_resolution() { ); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); + let computed_solution = dependency_provider.resolve("root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); @@ -168,7 +167,7 @@ fn conflict_with_partial_satisfier() { dependency_provider.add_dependencies("target", (1, 0, 0), []); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); + let computed_solution = dependency_provider.resolve("root", (1, 0, 0)).unwrap(); // Solution. let mut expected_solution = Map::default(); @@ -207,7 +206,7 @@ fn double_choices() { expected_solution.insert("d", 0u32); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "a", 0u32).unwrap(); + let computed_solution = dependency_provider.resolve("a", 0u32).unwrap(); assert_eq!(expected_solution, computed_solution); } @@ -230,24 +229,28 @@ fn confusing_with_lots_of_holes() { // This package is part of the dependency tree, but it's not part of the conflict dependency_provider.add_dependencies("baz", 1u32, vec![]); - let Err(PubGrubError::NoSolution(mut derivation_tree)) = - resolve(&dependency_provider, "root", 1u32) - else { + let Err(PubGrubError::NoSolution(mut error)) = dependency_provider.resolve("root", 1u32) else { unreachable!() }; assert_eq!( - &DefaultStringReporter::report(&derivation_tree), - r#"Because there is no available version for bar and foo 1 | 2 | 3 | 4 | 5 depends on bar, foo 1 | 2 | 3 | 4 | 5 is forbidden. -And because there is no version of foo in <1 | >1, <2 | >2, <3 | >3, <4 | >4, <5 | >5 and root 1 depends on foo, root 1 is forbidden."# + &DefaultStringReporter::report(&error, &dependency_provider), + r#"Because foo @ * depends on bar @ ∅ and root @ 1 depends on foo @ *, root @ 1 is forbidden."# ); - derivation_tree.collapse_no_versions(); + + error.derivation_tree.collapse_no_versions(); assert_eq!( - &DefaultStringReporter::report(&derivation_tree), - "Because foo depends on bar and root 1 depends on foo, root 1 is forbidden." + &DefaultStringReporter::report(&error, &dependency_provider), + r#"Because foo @ * depends on bar @ ∅ and root @ 1 depends on foo @ *, root @ 1 is forbidden."# ); + assert_eq!( - derivation_tree.packages(), + error + .derivation_tree + .packages() + .into_iter() + .filter_map(|p| error.package_store.pkg(p).unwrap().inner_pkg().copied()) + .collect::>(), // baz isn't shown. - Set::from_iter(&["root", "foo", "bar"]) + Set::from_iter(["root", "foo", "bar"]), ); } diff --git a/tests/proptest.rs b/tests/proptest.rs index 65d4753b..ce968f1c 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -2,63 +2,86 @@ #![allow(clippy::type_complexity)] -use std::collections::BTreeSet as Set; +mod sat_dependency_provider; + use std::convert::Infallible; use std::fmt::{Debug, Display}; +use std::hash::Hash; -use proptest::collection::{btree_map, btree_set, vec}; -use proptest::prelude::*; -use proptest::sample::Index; -use proptest::string::string_regex; - +use proptest::{ + collection::{btree_map, btree_set, vec}, + prelude::*, + sample::Index, + string::string_regex, +}; use pubgrub::{ - resolve, DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, - OfflineDependencyProvider, Package, PubGrubError, Ranges, Reporter, SelectedDependencies, - VersionSet, + helpers::PackageVersionWrapper, resolve, DefaultStringReporter, Dependencies, + DependencyProvider, DerivationTree, External, Map, NoSolutionError, OfflineDependencyProvider, + PackageArena, PackageId, PubGrubError, Ranges, Reporter, SelectedDependencies, Set, + VersionIndex, VersionRanges, VersionSet, }; use crate::sat_dependency_provider::SatResolve; -mod sat_dependency_provider; - /// The same as [OfflineDependencyProvider] but takes versions from the opposite end: /// if [OfflineDependencyProvider] returns versions from newest to oldest, this returns them from oldest to newest. #[derive(Clone)] -struct OldestVersionsDependencyProvider( - OfflineDependencyProvider, +struct OldestVersionsDependencyProvider( + OfflineDependencyProvider, ); -impl DependencyProvider for OldestVersionsDependencyProvider { +impl DependencyProvider + for OldestVersionsDependencyProvider +{ fn get_dependencies( - &self, - p: &P, - v: &VS::V, - ) -> Result, Infallible> { - self.0.get_dependencies(p, v) + &mut self, + pid: PackageId, + v: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, Infallible> { + self.0.get_dependencies(pid, v, package_store) } - fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { - Ok(self - .0 - .versions(package) - .into_iter() - .flatten() - .find(|&v| range.contains(v)) - .cloned()) + fn choose_version( + &mut self, + _: PackageId, + set: VersionSet, + _: &PackageArena, + ) -> Result, Infallible> { + Ok(set.first()) } - type Priority = as DependencyProvider>::Priority; + type Priority = as DependencyProvider>::Priority; - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { - self.0.prioritize(package, range) + fn prioritize( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Self::Priority { + self.0.prioritize(package_id, set, package_store) } type Err = Infallible; - type P = P; - type V = VS::V; - type VS = VS; - type M = String; + type P = PackageVersionWrapper

; + type M = &'static str; + + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a { + self.0.package_version_display(package, version_index) + } + + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a { + self.0.package_version_set_display(package, version_set) + } } /// The same as DP but it has a timeout. @@ -83,14 +106,15 @@ impl TimeoutDependencyProvider { impl DependencyProvider for TimeoutDependencyProvider { fn get_dependencies( - &self, - p: &DP::P, - v: &DP::V, - ) -> Result, DP::Err> { - self.dp.get_dependencies(p, v) + &mut self, + pid: PackageId, + version_index: VersionIndex, + package_store: &mut PackageArena, + ) -> Result, DP::Err> { + self.dp.get_dependencies(pid, version_index, package_store) } - fn should_cancel(&self) -> Result<(), DP::Err> { + fn should_cancel(&mut self) -> Result<(), DP::Err> { assert!(self.start_time.elapsed().as_secs() < 60); let calls = self.call_count.get(); assert!(calls < self.max_calls); @@ -98,37 +122,58 @@ impl DependencyProvider for TimeoutDependencyProvider Result, DP::Err> { - self.dp.choose_version(package, range) + fn choose_version( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Result, DP::Err> { + self.dp.choose_version(package_id, set, package_store) } type Priority = DP::Priority; - fn prioritize(&self, package: &DP::P, range: &DP::VS) -> Self::Priority { - self.dp.prioritize(package, range) + fn prioritize( + &mut self, + package_id: PackageId, + set: VersionSet, + package_store: &PackageArena, + ) -> Self::Priority { + self.dp.prioritize(package_id, set, package_store) } type Err = DP::Err; type P = DP::P; - type V = ::V; - type VS = DP::VS; type M = DP::M; + + fn package_version_display<'a>( + &'a self, + package: &'a Self::P, + version_index: VersionIndex, + ) -> impl Display + 'a { + self.dp.package_version_display(package, version_index) + } + + fn package_version_set_display<'a>( + &'a self, + package: &'a Self::P, + version_set: VersionSet, + ) -> impl Display + 'a { + self.dp.package_version_set_display(package, version_set) + } } fn timeout_resolve( dependency_provider: DP, - name: DP::P, - version: impl Into, + pkg: DP::P, + version_index: VersionIndex, ) -> Result< SelectedDependencies>, PubGrubError>, > { - resolve( - &TimeoutDependencyProvider::new(dependency_provider, 50_000), - name, - version, - ) + let mut dp = TimeoutDependencyProvider::new(dependency_provider, 50_000); + resolve(&mut dp, pkg, version_index) } type NumVS = Ranges; @@ -140,11 +185,9 @@ fn should_cancel_can_panic() { dependency_provider.add_dependencies(0, 0u32, [(666, Ranges::full())]); // Run the algorithm. - let _ = resolve( - &TimeoutDependencyProvider::new(dependency_provider, 1), - 0, - 0u32, - ); + let (p, v) = dependency_provider.resolve_parameters(0, 0u32).unwrap(); + let mut dp = TimeoutDependencyProvider::new(dependency_provider, 1); + let _ = resolve(&mut dp, p, v); } fn string_names() -> impl Strategy { @@ -161,7 +204,7 @@ fn string_names() -> impl Strategy { /// This generates a random registry index. /// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) /// This strategy has a high probability of having valid dependencies -pub fn registry_strategy( +pub fn registry_strategy( name: impl Strategy, ) -> impl Strategy, Vec<(N, u32)>)> { let max_crates = 40; @@ -184,9 +227,8 @@ pub fn registry_strategy( let raw_dependency = (any::(), any::(), raw_version_range); fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { - use std::cmp::{max, min}; let (a, b) = (a.index(size), b.index(size)); - (min(a, b), max(a, b)) + (a.min(b), a.max(b)) } let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); @@ -240,7 +282,7 @@ pub fn registry_strategy( let mut dependency_provider = OfflineDependencyProvider::::new(); - let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len()); + let complicated_len = complicated_len.min(list_of_pkgid.len()); let complicated: Vec<_> = if reverse_alphabetical { &list_of_pkgid[..complicated_len] } else { @@ -262,15 +304,14 @@ pub fn registry_strategy( /// Ensures that generator makes registries with large dependency trees. #[test] fn meta_test_deep_trees_from_strategy() { - use proptest::strategy::ValueTree; - use proptest::test_runner::TestRunner; + use proptest::{strategy::ValueTree, test_runner::TestRunner}; let mut dis = [0; 21]; let strategy = registry_strategy(0u16..665); let mut test_runner = TestRunner::deterministic(); for _ in 0..128 { - let (dependency_provider, cases) = strategy + let (mut dependency_provider, cases) = strategy .new_tree(&mut TestRunner::new_with_rng( Default::default(), test_runner.new_rng(), @@ -279,10 +320,10 @@ fn meta_test_deep_trees_from_strategy() { .current(); for (name, ver) in cases { - let res = resolve(&dependency_provider, name, ver); + let res = dependency_provider.resolve(name, ver); dis[res .as_ref() - .map(|x| std::cmp::min(x.len(), dis.len()) - 1) + .map(|x| x.len().min(dis.len()) - 1) .unwrap_or(0)] += 1; if dis.iter().all(|&x| x > 0) { return; @@ -304,22 +345,30 @@ fn meta_test_deep_trees_from_strategy() { /// then there must still be no solution with some options removed. /// If there was a solution to a resolution in the original dependency provider, /// there may not be a solution after versions are removes iif removed versions were critical for all valid solutions. -fn retain_versions( - dependency_provider: &OfflineDependencyProvider, - mut retain: impl FnMut(&N, &VS::V) -> bool, -) -> OfflineDependencyProvider { +fn retain_versions( + dependency_provider: &OfflineDependencyProvider, + retain: impl Fn(&N, &R::V) -> bool, +) -> OfflineDependencyProvider { let mut smaller_dependency_provider = OfflineDependencyProvider::new(); - for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { if !retain(n, v) { continue; } - let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unavailable(_) => panic!(), - Dependencies::Available(deps) => deps, - }; - smaller_dependency_provider.add_dependencies(n.clone(), v.clone(), deps) + let deps = dependency_provider.dependencies(n, v).unwrap(); + smaller_dependency_provider.add_dependencies( + n.clone(), + v.clone(), + deps.iter().map(|(p, r)| { + let r = R::from_ordered_versions( + dependency_provider + .versions(p) + .unwrap() + .map(|v| (v.clone(), r.contains(v))), + ); + (p.clone(), r) + }), + ) } } smaller_dependency_provider @@ -332,25 +381,28 @@ fn retain_versions( /// then there must still be a solution after dependencies are removed. /// If there was no solution to a resolution in the original dependency provider, /// there may now be a solution after dependencies are removed. -fn retain_dependencies( - dependency_provider: &OfflineDependencyProvider, - mut retain: impl FnMut(&N, &VS::V, &N) -> bool, -) -> OfflineDependencyProvider { +fn retain_dependencies( + dependency_provider: &OfflineDependencyProvider, + retain: impl Fn(&N, &R::V, &N) -> bool, +) -> OfflineDependencyProvider { let mut smaller_dependency_provider = OfflineDependencyProvider::new(); for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { - let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unavailable(_) => panic!(), - Dependencies::Available(deps) => deps, - }; + let deps = dependency_provider.dependencies(n, v).unwrap(); smaller_dependency_provider.add_dependencies( n.clone(), v.clone(), - deps.iter().filter_map(|(dep, range)| { + deps.iter().filter_map(|(dep, r)| { if !retain(n, v, dep) { None } else { - Some((dep.clone(), range.clone())) + let r = R::from_ordered_versions( + dependency_provider + .versions(dep) + .unwrap() + .map(|v| (v.clone(), r.contains(v))), + ); + Some((dep.clone(), r)) } }), ); @@ -359,44 +411,78 @@ fn retain_dependencies( smaller_dependency_provider } -fn errors_the_same_with_only_report_dependencies( +fn errors_the_same_with_only_report_dependencies( dependency_provider: OfflineDependencyProvider, name: N, ver: u32, ) { - let Err(PubGrubError::NoSolution(tree)) = - timeout_resolve(dependency_provider.clone(), name.clone(), ver) + let (p, v) = dependency_provider + .resolve_parameters(name.clone(), ver) + .unwrap(); + + let Err(PubGrubError::NoSolution(error)) = timeout_resolve(dependency_provider.clone(), p, v) else { return; }; - fn recursive( - to_retain: &mut Vec<(N, VS, N)>, - tree: &DerivationTree, + fn recursive( + to_retain: &mut Map>>, + tree: &DerivationTree, + package_store: &PackageArena>, + dependency_provider: &OfflineDependencyProvider, ) { match tree { - DerivationTree::External(External::FromDependencyOf(n1, vs1, n2, _)) => { - to_retain.push((n1.clone(), vs1.clone(), n2.clone())); + &DerivationTree::External(External::FromDependencyOf(n1, vs1, n2, _)) => { + let pkg1 = package_store.pkg(n1).unwrap(); + let pkg2 = package_store.pkg(n2).unwrap(); + + if let Some(n1) = pkg1.inner_pkg() { + let n2 = match pkg2 { + PackageVersionWrapper::Pkg(p) => p.pkg(), + PackageVersionWrapper::VirtualPkg(vp) => vp.pkg(), + PackageVersionWrapper::VirtualDep(vd) => vd.pkg(), + }; + + for v in vs1.iter() { + let v1 = *dependency_provider + .versions(n1) + .unwrap() + .nth(pkg1.inner(v).unwrap().1 as usize) + .unwrap(); + + to_retain + .entry(n1.clone()) + .or_default() + .entry(v1) + .or_default() + .insert(n2.clone()); + } + } } DerivationTree::Derived(d) => { - recursive(to_retain, &*d.cause1); - recursive(to_retain, &*d.cause2); + recursive(to_retain, &*d.cause1, package_store, dependency_provider); + recursive(to_retain, &*d.cause2, package_store, dependency_provider); } _ => {} } } - let mut to_retain = Vec::new(); - recursive(&mut to_retain, &tree); + let mut to_retain = Map::default(); + recursive( + &mut to_retain, + &error.derivation_tree, + &error.package_store, + &dependency_provider, + ); let removed_provider = retain_dependencies(&dependency_provider, |p, v, d| { - to_retain - .iter() - .any(|(n1, vs1, n2)| n1 == p && vs1.contains(v) && n2 == d) + (|| to_retain.get(p)?.get(v)?.get(d))().is_some() }); + let (p, v) = removed_provider.resolve_parameters(name, ver).unwrap(); + assert!( - timeout_resolve(removed_provider.clone(), name, ver).is_err(), + timeout_resolve(removed_provider, p, v).is_err(), "The full index errored filtering to only dependencies in the derivation tree succeeded" ); } @@ -421,7 +507,8 @@ proptest! { (dependency_provider, cases) in registry_strategy(string_names()) ) { for (name, ver) in cases { - _ = timeout_resolve(dependency_provider.clone(), name, ver); + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + _ = timeout_resolve(dependency_provider.clone(), p, v); } } @@ -431,7 +518,8 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { - _ = timeout_resolve(dependency_provider.clone(), name, ver); + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + _ = timeout_resolve(dependency_provider.clone(), p, v); } } @@ -441,7 +529,8 @@ proptest! { ) { let mut sat = SatResolve::new(&dependency_provider); for (name, ver) in cases { - let res = timeout_resolve(dependency_provider.clone(), name, ver); + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + let res = timeout_resolve(dependency_provider.clone(), p, v); sat.check_resolve(&res, &name, &ver); } } @@ -461,15 +550,20 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { - let one = timeout_resolve(dependency_provider.clone(), name, ver); + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + let one = timeout_resolve(dependency_provider.clone(), p.clone(), v); for _ in 0..3 { - match (&one, &timeout_resolve(dependency_provider.clone(), name, ver)) { + match (&one, &timeout_resolve(dependency_provider.clone(), p.clone(), v)) { (Ok(l), Ok(r)) => assert_eq!(l, r), - (Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => { + (Err(PubGrubError::NoSolution(error_l)), Err(PubGrubError::NoSolution(error_r))) => { + let (error_l, error_r) = (error_l.clone(), error_r.clone()); + let error_l = NoSolutionError { package_store: error_l.package_store, derivation_tree: error_l.derivation_tree }; + let error_r = NoSolutionError { package_store: error_r.package_store, derivation_tree: error_r.derivation_tree }; prop_assert_eq!( - DefaultStringReporter::report(derivation_l), - DefaultStringReporter::report(derivation_r) - )}, + DefaultStringReporter::report(&error_l, &dependency_provider), + DefaultStringReporter::report(&error_r, &dependency_provider) + ); + } _ => panic!("not the same result") } } @@ -484,8 +578,9 @@ proptest! { ) { let reverse_provider = OldestVersionsDependencyProvider(dependency_provider.clone()); for (name, ver) in cases { - let l = timeout_resolve(dependency_provider.clone(), name, ver); - let r = timeout_resolve(reverse_provider.clone(), name, ver); + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + let l = timeout_resolve(dependency_provider.clone(), p.clone(), v); + let r = timeout_resolve(reverse_provider.clone(), p, v); match (&l, &r) { (Ok(_), Ok(_)) => (), (Err(_), Err(_)) => (), @@ -499,36 +594,31 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in vec((any::(), any::(), any::()), 1..10) ) { - let packages: Vec<_> = dependency_provider.packages().collect(); - let mut to_remove = Set::new(); + let packages: Vec<_> = dependency_provider.packages().copied().collect(); + let mut to_remove = Set::default(); for (package_idx, version_idx, dep_idx) in indexes_to_remove { - let package = package_idx.get(&packages); + let pkg = *package_idx.get(&packages); let versions: Vec<_> = dependency_provider - .versions(package) - .unwrap().collect(); - let version = version_idx.get(&versions); - let dependencies: Vec<(u16, NumVS)> = match dependency_provider - .get_dependencies(package, version) + .versions(&pkg) .unwrap() - { - Dependencies::Unavailable(_) => panic!(), - Dependencies::Available(d) => d.into_iter().collect(), - }; - if !dependencies.is_empty() { - to_remove.insert((package, **version, dep_idx.get(&dependencies).0)); + .copied() + .collect(); + let version = *version_idx.get(&versions); + let deps = dependency_provider.dependencies(&pkg, &version).unwrap().iter().collect::>(); + if !deps.is_empty() { + to_remove.insert((pkg, version, *dep_idx.get(&deps).0)); } } let removed_provider = retain_dependencies( &dependency_provider, - |p, v, d| {!to_remove.contains(&(&p, *v, *d))} + |&p, &v, &d| !to_remove.contains(&(p, v, d)) ); for (name, ver) in cases { - if timeout_resolve(dependency_provider.clone(), name, ver).is_ok() { + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + if timeout_resolve(dependency_provider.clone(), p.clone(), v).is_ok() { prop_assert!( - timeout_resolve(removed_provider.clone(), name, ver).is_ok(), - "full index worked for `{} = \"={}\"` but removing some deps broke it!", - name, - ver, + timeout_resolve(removed_provider.clone(), p, v).is_ok(), + "full index worked for `{name} = \"={ver}\"` but removing some deps broke it!" ) } } @@ -541,44 +631,42 @@ proptest! { ) { let all_versions: Vec<(u16, u32)> = dependency_provider .packages() - .flat_map(|&p| { - dependency_provider - .versions(&p) - .unwrap() - .map(move |&v| (p, v)) - }) + .flat_map(|&p| dependency_provider.versions(&p).unwrap().map(move |&v| (p, v))) .collect(); let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect(); for (name, ver) in cases { - match timeout_resolve(dependency_provider.clone(), name, ver) { + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + match timeout_resolve(dependency_provider.clone(), p.clone(), v) { Ok(used) => { + let used_packages = used + .iter() + .filter_map(|(wrapper, &version_index)| wrapper.inner(version_index)) + .map(|(p, v)| (p, *dependency_provider.versions(p).unwrap().nth(v as usize).unwrap())) + .collect::>(); // If resolution was successful, then unpublishing a version of a crate // that was not selected should not change that. - let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { - used.get(n) == Some(v) // it was used - || !to_remove.contains(&(*n, *v)) // or it is not one to be removed - }); + let smaller_dependency_provider = retain_versions(&dependency_provider, |&n, &v| { + used_packages.get(&n) == Some(&v) // it was used + || !to_remove.contains(&(n, v)) // or it is not one to be removed + }); + let (p, v) = smaller_dependency_provider.resolve_parameters(name, ver).unwrap(); prop_assert!( - timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_ok(), - "unpublishing {:?} stopped `{} = \"={}\"` from working", - to_remove, - name, - ver + timeout_resolve(smaller_dependency_provider.clone(), p, v).is_ok(), + "unpublishing {to_remove:?} stopped `{name} = \"={ver}\"` from working" ) } Err(_) => { // If resolution was unsuccessful, then it should stay unsuccessful // even if any version of a crate is unpublished. - let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { - to_remove.contains(&(*n, *v)) // it is one to be removed + let smaller_dependency_provider = retain_versions(&dependency_provider, |&n, &v| { + to_remove.contains(&(n, v)) // it is one to be removed }); - prop_assert!( - timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_err(), - "full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!", - name, - ver, - to_remove, - ) + if let Some((p, v)) = smaller_dependency_provider.resolve_parameters(name, ver) { + prop_assert!( + timeout_resolve(smaller_dependency_provider.clone(), p, v).is_err(), + "full index did not work for `{name} = \"={ver}\"` but unpublishing {to_remove:?} fixed it!" + ) + } } } } @@ -594,26 +682,21 @@ fn large_case() { eprint!("{} ", name); let data = std::fs::read_to_string(&case).unwrap(); let start_time = std::time::Instant::now(); - if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { - let dependency_provider: OfflineDependencyProvider = + if name.ends_with("u16_NumberVersion.ron") { + let mut dependency_provider: OfflineDependencyProvider = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); - for p in dependency_provider.packages() { - for &v in dependency_provider.versions(p).unwrap() { - let res = resolve(&dependency_provider, *p, v); - sat.check_resolve(&res, p, &v); - } - } - } else if name.ends_with("str_SemanticVersion.ron") { - let dependency_provider: OfflineDependencyProvider< - &str, - Ranges, - > = ron::de::from_str(&data).unwrap(); - let mut sat = SatResolve::new(&dependency_provider); - for p in dependency_provider.packages() { - for v in dependency_provider.versions(p).unwrap() { - let res = resolve(&dependency_provider, *p, v); - sat.check_resolve(&res, p, v); + let packages = dependency_provider.packages().cloned().collect::>(); + for name in packages { + let versions = dependency_provider + .versions(&name) + .unwrap() + .copied() + .collect::>(); + for ver in versions { + let (p, v) = dependency_provider.resolve_parameters(name, ver).unwrap(); + let res = resolve(&mut dependency_provider, p, v); + sat.check_resolve(&res, &name, &ver); } } } diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index a1403d86..1a460b3c 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 +use std::collections::BTreeMap; +use std::fmt::{Debug, Display}; +use std::hash::Hash; + use pubgrub::{ - Dependencies, DependencyProvider, Map, OfflineDependencyProvider, Package, PubGrubError, - SelectedDependencies, VersionSet, + helpers::PackageVersionWrapper, DependencyProvider, Map, OfflineDependencyProvider, + PubGrubError, SelectedDependencies, VersionRanges, }; use varisat::ExtendFormula; @@ -35,17 +39,17 @@ fn sat_at_most_one(solver: &mut impl ExtendFormula, vars: &[varisat::Var]) { /// /// The SAT library does not optimize for the newer version, /// so the selected packages may not match the real resolver. -pub struct SatResolve { +pub struct SatResolve { solver: varisat::Solver<'static>, - all_versions_by_p: Map>, + all_versions_by_p: Map>, } -impl SatResolve { - pub fn new(dp: &OfflineDependencyProvider) -> Self { +impl SatResolve { + pub fn new>(dp: &OfflineDependencyProvider) -> Self { let mut cnf = varisat::CnfFormula::new(); let mut all_versions = vec![]; - let mut all_versions_by_p: Map> = Map::default(); + let mut all_versions_by_p: Map> = Map::default(); for p in dp.packages() { let mut versions_for_p = vec![]; @@ -56,7 +60,7 @@ impl SatResolve { all_versions_by_p .entry(p.clone()) .or_default() - .push((v.clone(), new_var)); + .insert(v.clone(), new_var); } // no two versions of the same package sat_at_most_one(&mut cnf, &versions_for_p); @@ -64,17 +68,13 @@ impl SatResolve { // active packages need each of there `deps` to be satisfied for (p, v, var) in &all_versions { - let deps = match dp.get_dependencies(p, v).unwrap() { - Dependencies::Unavailable(_) => panic!(), - Dependencies::Available(d) => d, - }; - for (p1, range) in &deps { - let empty_vec = vec![]; + let deps = dp.dependencies(p, v).unwrap(); + for (p1, range) in deps { let mut matches: Vec = all_versions_by_p .get(p1) - .unwrap_or(&empty_vec) + .unwrap_or(&BTreeMap::new()) .iter() - .filter(|(v1, _)| range.contains(v1)) + .filter(|&(v1, _)| range.contains(v1)) .map(|(_, var1)| var1.positive()) .collect(); // ^ the `dep` is satisfied or @@ -99,32 +99,35 @@ impl SatResolve { } } - pub fn resolve(&mut self, name: &P, ver: &VS::V) -> bool { + pub fn resolve(&mut self, name: &P, ver: &V) -> bool { if let Some(vers) = self.all_versions_by_p.get(name) { - if let Some((_, var)) = vers.iter().find(|(v, _)| v == ver) { + if let Some((_, var)) = vers.iter().find(|&(v, _)| v == ver) { self.solver.assume(&[var.positive()]); - self.solver + return self + .solver .solve() - .expect("docs say it can't error in default config") - } else { - false + .expect("docs say it can't error in default config"); } - } else { - false } + false } - pub fn is_valid_solution>( + pub fn is_valid_solution>>( &mut self, pids: &SelectedDependencies, ) -> bool { + let pids = pids + .iter() + .filter_map(|(wrapper, &version_index)| wrapper.inner(version_index)) + .collect::>(); + let mut assumption = vec![]; for (p, vs) in &self.all_versions_by_p { - let pid_for_p = pids.get(p); - for (v, var) in vs { - assumption.push(var.lit(pid_for_p == Some(v))) + let pid_for_p = pids.get(p).map(|&v| v as usize); + for (i, var) in vs.values().enumerate() { + assumption.push(var.lit(pid_for_p == Some(i))) } } @@ -135,19 +138,15 @@ impl SatResolve { .expect("docs say it can't error in default config") } - pub fn check_resolve>( + pub fn check_resolve>>( &mut self, res: &Result, PubGrubError>, p: &P, - v: &VS::V, + v: &V, ) { match res { - Ok(s) => { - assert!(self.is_valid_solution::(s)); - } - Err(_) => { - assert!(!self.resolve(p, v)); - } + Ok(s) => assert!(self.is_valid_solution::(s)), + Err(_) => assert!(!self.resolve(p, v)), } } } diff --git a/tests/tests.rs b/tests/tests.rs index 8c7f0ff2..473fd58d 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; +use pubgrub::{OfflineDependencyProvider, PubGrubError, Ranges}; type NumVS = Ranges; @@ -17,9 +17,9 @@ fn same_result_on_repeated_runs() { let name = "a"; let ver: u32 = 0; - let one = resolve(&dependency_provider, name, ver); + let one = dependency_provider.resolve(name, ver); for _ in 0..10 { - match (&one, &resolve(&dependency_provider, name, ver)) { + match (&one, &dependency_provider.resolve(name, ver)) { (Ok(l), Ok(r)) => assert_eq!(l, r), _ => panic!("not the same result"), } @@ -31,13 +31,13 @@ fn should_always_find_a_satisfier() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::empty())]); assert!(matches!( - resolve(&dependency_provider, "a", 0u32), + dependency_provider.resolve("a", 0u32), Err(PubGrubError::NoSolution { .. }) )); dependency_provider.add_dependencies("c", 0u32, [("a", Ranges::full())]); assert!(matches!( - resolve(&dependency_provider, "c", 0u32), + dependency_provider.resolve("c", 0u32), Err(PubGrubError::NoSolution { .. }) )); } @@ -46,7 +46,7 @@ fn should_always_find_a_satisfier() { fn depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("a", Ranges::full())]); - assert!(resolve(&dependency_provider, "a", 0u32).is_ok()); + assert!(dependency_provider.resolve("a", 0u32).is_ok()); dependency_provider.add_dependencies("a", 66u32, [("a", Ranges::singleton(111u32))]); - assert!(resolve(&dependency_provider, "a", 66u32).is_err()); + assert!(dependency_provider.resolve("a", 66u32).is_err()); } diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index 8b19881b..d3c17f0b 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -305,7 +305,7 @@ impl Ranges { /// See [`Ranges`] for the invariants checked. fn check_invariants(self) -> Self { if cfg!(debug_assertions) { - for p in self.segments.as_slice().windows(2) { + for p in self.segments.windows(2) { assert!(end_before_start_with_gap(&p[0].1, &p[1].0)); } for (s, e) in self.segments.iter() {