From b1ce8010e2cb723e022d32110fff3dba10d5a320 Mon Sep 17 00:00:00 2001 From: NopeCharon <115794254+NopeCharon@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:37:37 +0530 Subject: [PATCH] fix: a range that matches none of a package's pre-release keys names the pin that works MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A package whose only orderable keys are pre-releases (compat.glad publishes 0.0.0-651a425 alone, compat.tray 0.0.0-8dd1358) can never be reached by a range: a range sees a pre-release only when it names one at the same numbers. `"*"` or `"^0.0"` against such a package failed with constraint '*' matches none of: [0.0.0-651a425] and nothing more. The exact-pin hint was given only for unorderable keys, while these keys are orderable and took the plain branch. When every orderable key is a pre-release, the error now says why the range misses and offers the exact pin, with the highest pre-release as its value: These keys are pre-releases, which a range matches only when it names a pre-release of the same version — pin one exactly: acme.pre = "0.0.0-651a425" A package that also publishes a release is unchanged: a range can reach the release, so a miss there is an ordinary one. e2e 196 covers `*` and `^0.0` over one pre-release key, the highest of several being offered, the offered pin resolving, and no hint when a release is published. docs/05 (both languages) states the rule. Closes #370. --- docs/05-dependencies.md | 5 +++ docs/zh/05-dependencies.md | 3 ++ src/pm/resolver.cppm | 12 +++++++ tests/e2e/196_version_identity_and_lock.sh | 38 ++++++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/docs/05-dependencies.md b/docs/05-dependencies.md index b14600239..a9e3952bd 100644 --- a/docs/05-dependencies.md +++ b/docs/05-dependencies.md @@ -128,6 +128,11 @@ and `"0.0"` is `>= 0.0.0, < 0.1.0` (before 2026.10.31.1 both matched 0.0.0 alone). The dependency conflict check and the address check of `--locked` read requirements the same way. +A range matches a pre-release key (`1.0.0-rc.1`) only when it names a +pre-release of the same version, so `*` or `^0.0` reaches none of the keys of a +package that publishes only pre-releases (`compat.glad` publishes +`0.0.0-651a425`). The error then names the exact pin to write. + ### `visibility` — whether a dependency's usage crosses this package's own boundary ```toml diff --git a/docs/zh/05-dependencies.md b/docs/zh/05-dependencies.md index a660ecd5f..25e2d9bb1 100644 --- a/docs/zh/05-dependencies.md +++ b/docs/zh/05-dependencies.md @@ -115,6 +115,9 @@ qux = ">=1.0, <2.0" # Range combination 不带运算符的部分写法同样按 caret 的通配规则:`"0"` 为 `>= 0.0.0, < 1.0.0`,`"0.0"` 为 `>= 0.0.0, < 0.1.0`(2026.10.31.1 之前 两者都只匹配 0.0.0)。依赖冲突检查与 `--locked` 的地址核对以同样的方式读取要求。 +范围只在写出同一版本的预发布时才匹配预发布键(`1.0.0-rc.1`),因此对只发布预发布键的包 +(`compat.glad` 只发布 `0.0.0-651a425`),`*` 或 `^0.0` 一个键也够不着;这时的错误会给出应写的精确版本。 + ### `visibility` —— 一个依赖的用法是否跨越本包自己的边界 ```toml diff --git a/src/pm/resolver.cppm b/src/pm/resolver.cppm index d96d3c1a8..748e32613 100644 --- a/src/pm/resolver.cppm +++ b/src/pm/resolver.cppm @@ -228,6 +228,18 @@ resolve_semver(std::string_view ns, std::string_view shortName, auto msg = std::format( "dependency '{}': constraint '{}' matches none of: [{}]", qname, constraint, join(literals)); + // A range sees a pre-release only when it names one at the same numbers, + // so when every orderable key is a pre-release (`compat.glad` publishes + // `0.0.0-651a425` alone) no `*` or `^0.0` can reach any of them: the + // remedy is the same exact pin as for an unorderable key (mcpp#370). + if (std::ranges::all_of(parsed, &vr::Version::isPrerelease)) { + const auto highest = std::ranges::max_element(parsed) - parsed.begin(); + msg += std::format( + "\n These keys are pre-releases, which a range matches only when " + "it names a pre-release of the same version — pin one exactly:" + "\n {} = \"{}\"", + qname, literals[static_cast(highest)]); + } if (!unorderable.empty()) msg += std::format("\n (also published, but not orderable: [{}]){}", join(unorderable), pin_hint(unorderable)); diff --git a/tests/e2e/196_version_identity_and_lock.sh b/tests/e2e/196_version_identity_and_lock.sh index be7c3bf5a..4fee33e73 100755 --- a/tests/e2e/196_version_identity_and_lock.sh +++ b/tests/e2e/196_version_identity_and_lock.sh @@ -54,6 +54,11 @@ mk_pkg jdk '{ ["latest"] = { ref = "25.0.4.7.1" }, # khistory's shape: the only real release has an unorderable key. mk_pkg kh '{ ["latest"] = { ref = "pre-v0.0.5" }, ["pre-v0.0.5"] = { url = "u", sha256 = "0" } }' +# compat.glad's shape: the only key is a pre-release (mcpp#370). +mk_pkg pre '{ ["0.0.0-651a425"] = { url = "u", sha256 = "0" } }' +# Only pre-releases, more than one: the pin offered is the highest. +mk_pkg rc '{ ["1.0.0-rc.1"] = { url = "u", sha256 = "0" }, + ["1.0.0-rc.2"] = { url = "u", sha256 = "0" } }' # jdk-temurin's shape, but with two REAL entries: a genuine precedence tie. mk_pkg tie '{ ["1.0.0+a"] = { url = "u", sha256 = "0" }, ["1.0.0+b"] = { url = "u", sha256 = "0" } }' @@ -139,6 +144,39 @@ use_dep kh '=pre-v0.0.5' grep -qE 'invalid version constraint|not ordered versions' b5b.log \ && fail "'=' must resolve to that key" b5b.log +# ── 4b. Only pre-releases: no range reaches them, so the error says what does ── +# The keys are orderable, so this is not the unorderable branch above, but a +# range still sees a pre-release only when it names one (mcpp#370). +for c in '*' '^0.0'; do + use_dep pre "$c" + "$MCPP" build > b4c.log 2>&1 && fail "'$c' must not reach a pre-release" b4c.log + grep -q 'matches none of' b4c.log || fail "expected '$c' to match none" b4c.log + grep -q 'These keys are pre-releases' b4c.log \ + || fail "expected the error to say why '$c' matches none" b4c.log + grep -q 'acme.pre = "0.0.0-651a425"' b4c.log \ + || fail "the error for '$c' must show the exact pin that works" b4c.log +done + +use_dep rc '^1.0' +"$MCPP" build > b4d.log 2>&1 && fail "'^1.0' must not reach a pre-release" b4d.log +grep -q 'acme.rc = "1.0.0-rc.2"' b4d.log \ + || fail "the pin offered must be the highest pre-release" b4d.log + +# The pin offered resolves (and fails later, at install, for want of a payload). +use_dep pre '0.0.0-651a425' +"$MCPP" build > b4f.log 2>&1 && fail "expected the unseeded payload to fail" b4f.log +grep -q 'matches none of' b4f.log \ + && fail "the exact pre-release pin the error offers must resolve" b4f.log +grep -q '0\.0\.0-651a425' b4f.log || fail "the exact key must be addressed literally" b4f.log + +# A release among the keys can be reached by a range, so a miss there is an +# ordinary one and carries no pin. +use_dep im '^2' +"$MCPP" build > b4e.log 2>&1 && fail "'^2' must match none of im's keys" b4e.log +grep -q 'matches none of' b4e.log || fail "expected '^2' to match none" b4e.log +grep -q 'pre-releases' b4e.log \ + && fail "a package that publishes a release must not get the pre-release pin" b4e.log + # ── 5. A genuine precedence tie is refused, not guessed ─────────────────── use_dep tie '^1.0' "$MCPP" build > b6.log 2>&1 && fail "a tie must not silently pick one" b6.log