Context and request
Observed behavior: both CompareTo and Equals guard their argument with if (-not $other -is [PSSemVer]). PowerShell applies -not to $other first, so the expression is ((-not $other) -is [PSSemVer]), which is a [bool] tested against [PSSemVer] and therefore always $false. The guard is dead code, and the method continues with property access on whatever object was passed.
Import-Module PSSemVer
([PSSemVer]'1.0.0').CompareTo(42) # 1 - expected: throws ArgumentException
([PSSemVer]'1.0.0').CompareTo(@{ Major = 9; Minor = 0; Patch = 0 }) # -1 - silently compares against a hashtable
-not 42 -is [PSSemVer] # False - demonstrates the precedence
Expected behavior: CompareTo throws [ArgumentException] for a non-[PSSemVer] argument, as its own message already states, and Equals returns $false rather than duck-typing arbitrary objects.
Reproduction: the snippet above.
Environment: PSSemVer 1.1.9, PowerShell 7.6.4. Not platform specific — the defect is in src/classes/public/PSSemVer.ps1.
Regression: no. The guard has been ineffective since it was written.
Workaround: cast explicitly before comparing, for example $a.CompareTo([PSSemVer]$b).
Acceptance criteria:
CompareTo throws [ArgumentException] when the argument is not a [PSSemVer] and cannot be converted to one.
Equals returns $false for a non-[PSSemVer] argument instead of comparing missing properties.
Sort-Object, -lt, -gt, -eq, and the existing suite still behave as they do today for real [PSSemVer] operands.
Technical decisions
Root cause is bounded to the two guard expressions in src/classes/public/PSSemVer.ps1; the fix is if ($other -isnot [PSSemVer]). This is behaviour-changing for callers that currently rely on the silent duck-typed path, so it needs a release-impact decision: comparing a [PSSemVer] against a string works today only because PowerShell coerces the right-hand operand before CompareTo is reached, and that path must keep working. Verify that -lt/-gt against a valid version string still succeed after the change, and decide explicitly whether a hashtable or PSCustomObject with Major/Minor/Patch should keep comparing or start throwing.
Implementation plan
Add regression tests asserting the throw and the $false return, fix both guards, then re-run the suite including the coercion cases ([PSSemVer]'1.0.0' -lt '1.0.1') to confirm no user-visible comparison regressed.
Context and request
Observed behavior: both
CompareToandEqualsguard their argument withif (-not $other -is [PSSemVer]). PowerShell applies-notto$otherfirst, so the expression is((-not $other) -is [PSSemVer]), which is a[bool]tested against[PSSemVer]and therefore always$false. The guard is dead code, and the method continues with property access on whatever object was passed.Expected behavior:
CompareTothrows[ArgumentException]for a non-[PSSemVer]argument, as its own message already states, andEqualsreturns$falserather than duck-typing arbitrary objects.Reproduction: the snippet above.
Environment: PSSemVer 1.1.9, PowerShell 7.6.4. Not platform specific — the defect is in
src/classes/public/PSSemVer.ps1.Regression: no. The guard has been ineffective since it was written.
Workaround: cast explicitly before comparing, for example
$a.CompareTo([PSSemVer]$b).Acceptance criteria:
CompareTothrows[ArgumentException]when the argument is not a[PSSemVer]and cannot be converted to one.Equalsreturns$falsefor a non-[PSSemVer]argument instead of comparing missing properties.Sort-Object,-lt,-gt,-eq, and the existing suite still behave as they do today for real[PSSemVer]operands.Technical decisions
Root cause is bounded to the two guard expressions in
src/classes/public/PSSemVer.ps1; the fix isif ($other -isnot [PSSemVer]). This is behaviour-changing for callers that currently rely on the silent duck-typed path, so it needs a release-impact decision: comparing a[PSSemVer]against a string works today only because PowerShell coerces the right-hand operand beforeCompareTois reached, and that path must keep working. Verify that-lt/-gtagainst a valid version string still succeed after the change, and decide explicitly whether a hashtable orPSCustomObjectwithMajor/Minor/Patchshould keep comparing or start throwing.Implementation plan
Add regression tests asserting the throw and the
$falsereturn, fix both guards, then re-run the suite including the coercion cases ([PSSemVer]'1.0.0' -lt '1.0.1') to confirm no user-visible comparison regressed.