diff --git a/source/dub/dependency.d b/source/dub/dependency.d index ddf1ca7..3f8de76 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -54,12 +54,12 @@ } /// A Dependency, which matches every valid version. - static @property Dependency any() { return Dependency(ANY_IDENT); } + static @property Dependency any() { return Dependency(VersionRange.Any); } /// An invalid dependency (with no possible version matches). static @property Dependency invalid() { - return Dependency(VersionRange(Version.maxRelease, Version.minRelease)); + return Dependency(VersionRange.Invalid); } /** Constructs a new dependency specification from a string @@ -145,7 +145,10 @@ @property void default_(bool value) { m_default = value; } /// Returns true $(I iff) the version range only matches a specific version. - @property bool isExactVersion() const { return this.m_range.m_versA == this.m_range.m_versB; } + @property bool isExactVersion() const scope @safe + { + return this.m_range.isExactVersion(); + } /// Determines whether it is a Git dependency. @property bool isSCM() const { return !repository.empty; } @@ -330,7 +333,7 @@ These methods are suitable for equality comparisons, as well as for using `Dependency` as a key in hash or tree maps. */ - bool opEquals(const Dependency o) + bool opEquals(scope const Dependency o) const { // TODO(mdondorff): Check if not comparing the path is correct for all clients. return this.m_range == o.m_range @@ -338,7 +341,7 @@ } /// ditto - int opCmp(const Dependency o) + int opCmp(scope const Dependency o) const { if (auto result = this.m_range.opCmp(o.m_range)) return result; @@ -374,12 +377,7 @@ This is true in particular for the `any` constant. */ - bool matchesAny() - const { - return this.m_range.m_inclusiveA && this.m_range.m_inclusiveB - && this.m_range.m_versA == Version.minRelease - && this.m_range.m_versB == Version.maxRelease; - } + bool matchesAny() const scope { return this.m_range.matchesAny(); } unittest { assert(Dependency("*").matchesAny); @@ -720,7 +718,6 @@ Semantic Versioning Specification v2.0.0 at http://semver.org/). */ struct Version { -@safe: private { static immutable MAX_VERS = "99999.0.0"; static immutable UNKNOWN_VERS = "unknown"; @@ -736,7 +733,7 @@ /** Constructs a new `Version` from its string representation. */ - this(string vers) + this(string vers) @safe pure { enforce(vers.length > 1, "Version strings must not be empty."); if (vers[0] != branchPrefix && !vers.isGitHash && vers.ptr !is UNKNOWN_VERS.ptr) @@ -749,35 +746,52 @@ This method is equivalent to calling the constructor and is used as an endpoint for the serialization framework. */ - static Version fromString(string vers) { return Version(vers); } + static Version fromString(string vers) @safe pure { return Version(vers); } - bool opEquals(const Version oth) const { return opCmp(oth) == 0; } + bool opEquals(scope const Version oth) const scope @safe pure + { + return opCmp(oth) == 0; + } /// Tests if this represents a hash instead of a version. - @property bool isSCM() const { return m_version.isGitHash; } + @property bool isSCM() const scope @safe pure nothrow @nogc + { + return m_version.isGitHash; + } /// Tests if this represents a branch instead of a version. - @property bool isBranch() const { return m_version.length > 0 && m_version[0] == branchPrefix; } + @property bool isBranch() const scope @safe pure nothrow @nogc + { + return m_version.length > 0 && m_version[0] == branchPrefix; + } /// Tests if this represents the master branch "~master". - @property bool isMaster() const { return m_version == masterString; } + @property bool isMaster() const scope @safe pure nothrow @nogc + { + return m_version == masterString; + } /** Tests if this represents a pre-release version. Note that branches are always considered pre-release versions. */ - @property bool isPreRelease() const { + @property bool isPreRelease() const scope @safe pure nothrow @nogc + { if (isBranch || isSCM) return true; return isPreReleaseVersion(m_version); } /// Tests if this represents the special unknown version constant. - @property bool isUnknown() const { return m_version == UNKNOWN_VERS; } + @property bool isUnknown() const scope @safe pure nothrow @nogc + { + return m_version == UNKNOWN_VERS; + } /** Tests two versions for equality, according to the selected match mode. */ bool matches(Version other, VersionMatchMode mode = VersionMatchMode.standard) - const { + const scope @safe pure + { if (this != other) return false; @@ -794,8 +808,8 @@ compared using SemVer semantics, while branches are compared lexicographically. */ - int opCmp(ref const Version other) - const { + int opCmp(scope ref const Version other) const scope @safe pure + { if (isUnknown || other.isUnknown) { throw new Exception("Can't compare unknown versions! (this: %s, other: %s)".format(this, other)); } @@ -818,10 +832,16 @@ return compareVersions(m_version, other.m_version); } /// ditto - int opCmp(const Version other) const { return opCmp(other); } + int opCmp(scope const Version other) const scope @safe pure + { + return this.opCmp(other); + } /// Returns the string representation of the version/branch. - string toString() const { return m_version; } + string toString() const return scope @safe pure nothrow @nogc + { + return m_version; + } } /// A range of versions that are acceptable @@ -832,8 +852,13 @@ bool m_inclusiveA = true; // A comparison > (true) or >= (false) bool m_inclusiveB = true; // B comparison < (true) or <= (false) + /// Matches any version + public static immutable Any = VersionRange(Version.minRelease, Version.maxRelease); + /// Doesn't match any version + public static immutable Invalid = VersionRange(Version.maxRelease, Version.minRelease); + /// - public int opCmp (const VersionRange o) const @safe + public int opCmp (scope const VersionRange o) const scope @safe { if (m_inclusiveA != o.m_inclusiveA) return m_inclusiveA < o.m_inclusiveA ? -1 : 1; if (m_inclusiveB != o.m_inclusiveB) return m_inclusiveB < o.m_inclusiveB ? -1 : 1; @@ -868,6 +893,21 @@ this.m_versB = bcmp < 0 ? m_versB : o.m_versB; } + /// Returns true $(I iff) the version range only matches a specific version. + @property bool isExactVersion() const scope @safe + { + return this.m_versA == this.m_versB; + } + + /// Determines if this dependency specification matches arbitrary versions. + /// This is true in particular for the `any` constant. + public bool matchesAny() const scope @safe + { + return this.m_inclusiveA && this.m_inclusiveB + && this.m_versA == Version.minRelease + && this.m_versB == Version.maxRelease; + } + public static VersionRange fromString (string ves) @safe { static import std.string; diff --git a/source/dub/semver.d b/source/dub/semver.d index c04a9c5..674b324 100644 --- a/source/dub/semver.d +++ b/source/dub/semver.d @@ -24,7 +24,7 @@ /** Validates a version string according to the SemVer specification. */ -bool isValidVersion(string ver) +bool isValidVersion(scope string ver) pure @nogc { // NOTE: this is not by spec, but to ensure sane input if (ver.length > 256) return false; @@ -101,7 +101,7 @@ /** Determines if a given valid SemVer version has a pre-release suffix. */ -bool isPreReleaseVersion(string ver) pure @nogc +bool isPreReleaseVersion(scope string ver) pure @nogc nothrow in { assert(isValidVersion(ver)); } do { foreach (i; 0 .. 2) { @@ -134,16 +134,30 @@ Returns a negative number if `a` is a lower version than `b`, `0` if they are equal, and a positive number otherwise. */ -int compareVersions(string a, string b) +int compareVersions(scope string a, scope string b) pure @nogc { + // This needs to be a nested function as we can't pass local scope + // variables by `ref` + int compareNumber() @safe pure @nogc { + int res = 0; + while (true) { + if (a[0] != b[0] && res == 0) res = a[0] - b[0]; + a = a[1 .. $]; b = b[1 .. $]; + auto aempty = !a.length || (a[0] < '0' || a[0] > '9'); + auto bempty = !b.length || (b[0] < '0' || b[0] > '9'); + if (aempty != bempty) return bempty - aempty; + if (aempty) return res; + } + } + // compare a.b.c numerically - if (auto ret = compareNumber(a, b)) return ret; + if (auto ret = compareNumber()) return ret; assert(a[0] == '.' && b[0] == '.'); a = a[1 .. $]; b = b[1 .. $]; - if (auto ret = compareNumber(a, b)) return ret; + if (auto ret = compareNumber()) return ret; assert(a[0] == '.' && b[0] == '.'); a = a[1 .. $]; b = b[1 .. $]; - if (auto ret = compareNumber(a, b)) return ret; + if (auto ret = compareNumber()) return ret; // give precedence to non-prerelease versions bool apre = a.length > 0 && a[0] == '-'; @@ -314,7 +328,7 @@ assert("1.0.0-pre.release+meta" == expandVersion("1-pre.release+meta")); } -private int compareIdentifier(ref string a, ref string b) +private int compareIdentifier(scope ref string a, scope ref string b) pure @nogc { bool anumber = true; bool bnumber = true; @@ -344,20 +358,7 @@ } } -private int compareNumber(ref string a, ref string b) -pure @nogc { - int res = 0; - while (true) { - if (a[0] != b[0] && res == 0) res = a[0] - b[0]; - a = a[1 .. $]; b = b[1 .. $]; - auto aempty = !a.length || (a[0] < '0' || a[0] > '9'); - auto bempty = !b.length || (b[0] < '0' || b[0] > '9'); - if (aempty != bempty) return bempty - aempty; - if (aempty) return res; - } -} - -private bool isValidIdentifierChain(string str, bool allow_leading_zeros = false) +private bool isValidIdentifierChain(scope string str, bool allow_leading_zeros = false) pure @nogc { if (str.length == 0) return false; while (str.length) { @@ -370,7 +371,7 @@ return true; } -private bool isValidIdentifier(string str, bool allow_leading_zeros = false) +private bool isValidIdentifier(scope string str, bool allow_leading_zeros = false) pure @nogc { if (str.length < 1) return false; @@ -394,7 +395,7 @@ } private bool isValidNumber(string str) -pure @nogc { +pure @nogc nothrow { if (str.length < 1) return false; foreach (ch; str) if (ch < '0' || ch > '9') @@ -406,7 +407,7 @@ return true; } -private ptrdiff_t indexOfAny(string str, in char[] chars) +private ptrdiff_t indexOfAny(scope string str, in char[] chars) pure @nogc { ptrdiff_t ret = -1; foreach (ch; chars) {