From d5444b3297766f64e6fa24b92f9a461352dd0956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Mon, 12 Oct 2026 22:52:07 +0200 Subject: [PATCH] Key already returned dependencies on repo and package PackageLister.dependencies_as_incompatibilities/4 read already_returned with a {repo, package} key but wrote it with the package name, so the lookup never matched. Revisiting a version of a package returned the same dependency incompatibilities again, and the solver added the duplicates to its incompatibility set during backtracking. --- lib/hex_solver/package_lister.ex | 3 +- test/hex_solver/package_lister_test.exs | 41 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/hex_solver/package_lister_test.exs diff --git a/lib/hex_solver/package_lister.ex b/lib/hex_solver/package_lister.ex index 6c33786..4a76abd 100644 --- a/lib/hex_solver/package_lister.ex +++ b/lib/hex_solver/package_lister.ex @@ -133,7 +133,8 @@ defmodule HexSolver.PackageLister do lister = %{ lister | already_prefetched: MapSet.union(lister.already_prefetched, prefetch), - already_returned: Map.put(lister.already_returned, package, already_returned) + already_returned: + Map.put(lister.already_returned, {package_repo, package}, already_returned) } {lister, incompatibilities} diff --git a/test/hex_solver/package_lister_test.exs b/test/hex_solver/package_lister_test.exs new file mode 100644 index 0000000..e44f889 --- /dev/null +++ b/test/hex_solver/package_lister_test.exs @@ -0,0 +1,41 @@ +defmodule HexSolver.PackageListerTest do + use HexSolver.Case, async: true + + alias HexSolver.PackageLister + alias HexSolver.Registry.Process, as: Registry + + describe "dependencies_as_incompatibilities/4" do + test "skips dependencies already returned for the version" do + Registry.put("foo", "1.0.0", [{"bar", "~> 1.0"}]) + Registry.put("foo", "2.0.0", [{"bar", "~> 1.0"}]) + Registry.put("bar", "1.0.0", []) + Registry.prefetch([{nil, "foo"}]) + + lister = %PackageLister{registry: Registry} + + assert {lister, [incompatibility]} = + PackageLister.dependencies_as_incompatibilities(lister, nil, "foo", v("2.0.0")) + + assert to_string(incompatibility) == + ~s(every version of "foo" depends on "bar ~> 1.0") + + assert {_lister, []} = + PackageLister.dependencies_as_incompatibilities(lister, nil, "foo", v("1.0.0")) + end + + test "returns dependencies again for a package with the same name in another repo" do + Registry.put("foo", "1.0.0", [{"bar", "~> 1.0"}]) + Registry.put("repo", "foo", "1.0.0", [{"bar", "~> 1.0"}]) + Registry.put("bar", "1.0.0", []) + Registry.prefetch([{nil, "foo"}, {"repo", "foo"}]) + + lister = %PackageLister{registry: Registry} + + assert {lister, [_incompatibility]} = + PackageLister.dependencies_as_incompatibilities(lister, nil, "foo", v("1.0.0")) + + assert {_lister, [_incompatibility]} = + PackageLister.dependencies_as_incompatibilities(lister, "repo", "foo", v("1.0.0")) + end + end +end