Skip to content

Fix the dead type guard in PSSemVer.CompareTo and PSSemVer.Equals #48

Description

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugBroken functionality that fails or creates an unwanted outcomePatchFixes bugs or adds small fixes to existing functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions