From e9ecee7daded0dcb97d050b42f0b6df500e094ed Mon Sep 17 00:00:00 2001 From: Adrien Cotte Date: Tue, 15 Sep 2026 15:20:10 +0200 Subject: [PATCH] Accept abbreviated upper bounds in version ranges Range matching already includes more precise versions of an abbreviated upper bound, but parsing rejects ranges whose lower bound extends that upper bound. Allow these ranges so callers can constrain a minimum version to the same major or minor version without inventing an upper limit. Fixes #698 Assisted-by: Codex:gpt-6-astra Signed-off-by: Adrien Cotte --- NEWS.rst | 2 ++ doc/source/module.rst | 3 +++ tcl/modspec.tcl | 4 +++- .../272-adv_version_spec-range.exp | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 5601b8078..ba5e14852 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -251,6 +251,8 @@ Modules 5.7.0 (not yet released) * Doc: add the :ref:`user-guide` document that explains a selection of useful but lesser known features through practical examples and common use cases. +* Accept abbreviated upper bounds in version ranges such as ``@3.20:3`` to + select versions starting at ``3.20`` within major version ``3``. * Init: fix command injection in Bash completion when module names contain shell meta-characters. Completion candidates were passed to ``compgen -W`` which evaluates command substitution syntax. (fix `CVE-2026-85013`_ found diff --git a/doc/source/module.rst b/doc/source/module.rst index b9ab6e5ed..e0f58d3e6 100644 --- a/doc/source/module.rst +++ b/doc/source/module.rst @@ -3235,6 +3235,9 @@ the activation of the extended default mechanism (see to refer to more precise version numbers like ``1.2.3``. Characters ``.`` and ``-`` are considered version number separator to determine abbreviated versions. Range of versions on its side natively handles abbreviated versions. +The upper bound may be less precise than the lower bound. An abbreviated +upper bound includes all versions that extend it, provided they also satisfy +the lower bound. In order to be specified in a range of versions or compared to a range of versions, the version major element should corresponds to a number. For diff --git a/tcl/modspec.tcl b/tcl/modspec.tcl index 97bf7e488..a614a449c 100644 --- a/tcl/modspec.tcl +++ b/tcl/modspec.tcl @@ -1246,7 +1246,9 @@ proc parseModuleVersionSpecifier {modspec} { set cmpspec le set versspec $hivers # between or equal - } elseif {[versioncmp $lovers $hivers] == 1} { + # An abbreviated upper bound includes its more precise versions. + } elseif {[versioncmp $lovers $hivers] == 1 && ![string match\ + $hivers[extendedDefaultCharGlobMatch] $lovers]} { set invalidversrange 1 } else { set cmpspec be diff --git a/testsuite/modules.70-maint/272-adv_version_spec-range.exp b/testsuite/modules.70-maint/272-adv_version_spec-range.exp index 0e4ac79de..16c0c8d12 100644 --- a/testsuite/modules.70-maint/272-adv_version_spec-range.exp +++ b/testsuite/modules.70-maint/272-adv_version_spec-range.exp @@ -84,6 +84,8 @@ testouterr_cmd sh {load mod@1::} ERR "$err_specvers'1::'" testouterr_cmd sh {load mod@1,:,} ERR "$err_specvers'1,:,'" testouterr_cmd sh {load mod@,:} ERR "$err_specvers',:'" testouterr_cmd sh {load mod@1.3:1.2} ERR "$err_rangevers'1.3:1.2'" +testouterr_cmd sh {load mod@4.1:3} ERR "$err_rangevers'4.1:3'" +testouterr_cmd sh {load mod@1.30:1.3} ERR "$err_rangevers'1.30:1.3'" testouterr_cmd sh {load mod@,<} ERR "$err_specvers',<'" testouterr_cmd sh {load mod@:<} ERR "$err_rangevers':<'" testouterr_cmd sh {load mod@1/1:2} ERR "$err_specvers'1/1:2'" @@ -113,6 +115,7 @@ testouterr_cmd sh {load extdfl @1.2:} $ans {} testouterr_cmd sh {load extdfl @:1.3.1} $ans {} testouterr_cmd sh {load extdfl @:1.3.7} $ans {} testouterr_cmd sh {load extdfl @1.2:1.3} $ans {} +testouterr_cmd sh {load extdfl @1.2:1} $ans {} testouterr_cmd sh {load extdfl @1.2.3:1.3.7} $ans {} testouterr_cmd sh {load extdfl @:2.0.1} $ans {} testouterr_cmd sh {load extdfl @:2.0} $ans {} @@ -187,6 +190,18 @@ testouterr_cmd sh {spider -t ext?fl @1.3:} OK "$mp:\nextdfl/1.3.1(default)\nextd testouterr_cmd sh {avail -t e??.* @:1.4} OK "$mp:\ne.t.fl/1.3.1\ne.t.fl/1.3.7\ne.t.fl/1.4.5" # additional version tests +# Abbreviated upper bounds apply independently of extended defaults. +foreach extdfl {0 1} { + setenv_var MODULES_EXTENDED_DEFAULT $extdfl + set ans [list [list text $mp/extdfl/1.3.7] [list text $mp/extdfl/1.4.5]] + testouterr_cmd sh {paths extdfl@1.3.7:1} $ans {} + testouterr_cmd sh {paths -i EXTDFL@1.3.7:1} $ans {} + set ans [list [list text $mp/extdfl/1.3.7]] + testouterr_cmd sh {paths extdfl@1.3.7:1.3} $ans {} + testouterr_cmd sh {paths extdfl@1.3.7:1.3,9:10} $ans {} +} +unsetenv_var MODULES_EXTENDED_DEFAULT + testouterr_cmd sh {avail -t extdfl8 @1.33:} OK "$mp:\nextdfl8/1.33" testouterr_cmd sh {avail -t extdfl8 @:1.3} OK "$mp:\nextdfl8/1.3.1\nextdfl8/1.3.7" testouterr_cmd sh {spider -t extdfl8 @:1.3} OK "$mp:\nextdfl8/1.3.1\nextdfl8/1.3.7"