Skip to content

Make Matcher contravariant to fix type checking (issue #222)#273

Open
brunns wants to merge 3 commits intohamcrest:mainfrom
brunns:fix-matcher-contravariance-issue-222
Open

Make Matcher contravariant to fix type checking (issue #222)#273
brunns wants to merge 3 commits intohamcrest:mainfrom
brunns:fix-matcher-contravariance-issue-222

Conversation

@brunns
Copy link
Member

@brunns brunns commented Feb 12, 2026

Summary

Fixes #222: Makes Matcher contravariant with respect to its type parameter, allowing more flexible and correct type assignments.

Problem

Previously, Matcher was invariant, which meant that Matcher[Sized] could not be assigned to Matcher[str] even though str is a Sized type. This violated the principle of contravariance for types that consume their type parameter.

Solution

  • Made Matcher's TypeVar contravariant: T = TypeVar("T", contravariant=True)
  • Added type: ignore[overload-overlap] to is_() overload to suppress mypy's overly strict overlap detection
  • The overloads work correctly at runtime; this is a mypy limitation with contravariant types

Example Now Working

# str is Sized, so Matcher[Sized] should be assignable to Matcher[str]
matcher: Matcher[str] = has_length(greater_than(0))  # ✅ Now works!

Testing

Added comprehensive type tests to ensure contravariance doesn't break existing matchers:

New Test Coverage (17 tests, all passing)

  • Core matchers: equal_to, is_, is_not, none, not_none, same_instance, instance_of, anything
  • Logical matchers: all_of, any_of
  • Collection matchers: has_item, has_items, contains_exactly, contains_inanyorder, has_entry, has_entries, has_key, has_value, empty, is_in, only_contains
  • Number matchers: greater_than, less_than, close_to, etc.
  • Object matchers: has_property, has_properties, has_length, has_string
  • Text matchers: contains_string, starts_with, ends_with, matches_regexp, etc.
  • Nested matchers: Combinations like has_item(greater_than(3))
  • Contravariance verification: Direct tests of contravariant assignment

Results

  • ✅ All mypy type checking passes
  • ✅ All 17 type-hinting tests pass
  • ✅ No functional regressions detected
  • ✅ All common matchers verified to work correctly

Note on Issue #234

While investigating #222, discovered that issue #234 (sequence matcher type warnings) appears to already be fixed in the current codebase. Added tests to verify.

🤖 Generated with Claude Code

@brunns brunns requested a deployment to publish-test-release February 12, 2026 13:37 — with GitHub Actions Waiting
Fixes hamcrest#222: Matchers should be contravariant with respect to their type
parameter. This allows more flexible type assignments where a Matcher[Base]
can be used where a Matcher[Derived] is expected, which is the correct
variance for types that consume their type parameter.

Changes:
- Make Matcher's TypeVar contravariant in src/hamcrest/core/matcher.py
- Add type: ignore comment to is_() overload to suppress mypy's overly
  strict overlap detection (the overloads work correctly at runtime)
- Add test cases for issues hamcrest#222 and hamcrest#234 in test_assert_that.yml
- Add comprehensive type tests for all common matchers to verify no
  regressions in tests/type-hinting/test_common_matchers.yml

Example now working:
  matcher: Matcher[str] = has_length(greater_than(0))

Since str is Sized, Matcher[Sized] can now be assigned to Matcher[str]
because Matcher is contravariant. This makes the type system more flexible
and correct.

Testing:
- All mypy type checking passes
- All type-hinting tests pass (17 tests covering all major matchers)
- Comprehensive tests added for core, logical, collection, number,
  object, and text matchers
- No functional regressions detected

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@brunns brunns force-pushed the fix-matcher-contravariance-issue-222 branch from 823a95d to 7ef70a8 Compare February 12, 2026 13:43
@brunns brunns had a problem deploying to publish-test-release February 12, 2026 13:43 — with GitHub Actions Failure
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@brunns brunns requested a deployment to publish-test-release February 12, 2026 14:04 — with GitHub Actions Waiting
@brunns brunns requested a deployment to publish-test-release February 12, 2026 14:21 — with GitHub Actions Waiting
@brunns
Copy link
Member Author

brunns commented Feb 12, 2026

Relies on #272 I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matchers should be contravariant

1 participant