Skip to content

fix: reject negative counts in exactly/at_least/at_most/between#1922

Merged
RobinPicard merged 1 commit into
dottxt-ai:mainfrom
chuenchen309:fix/reject-negative-quantify-counts
Jul 20, 2026
Merged

fix: reject negative counts in exactly/at_least/at_most/between#1922
RobinPicard merged 1 commit into
dottxt-ai:mainfrom
chuenchen309:fix/reject-negative-quantify-counts

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

What

QuantifyExact, QuantifyMinimum, and QuantifyMaximum (backing exactly(), at_least(), at_most()) accepted negative counts with no validation. QuantifyBetween (backing between()) validated min_count <= max_count but was also missing a non-negative check.

Why this matters

A negative count silently produces a regex quantifier that Python's re engine doesn't recognize as a quantifier at all (e.g. {-1}), so re falls back to treating it as literal text:

from outlines.types.dsl import exactly, to_regex
import re

term = exactly(-1, "a")
pattern = to_regex(term)          # '(a){-1}'
re.compile(pattern).fullmatch("a")        # False
re.compile(pattern).fullmatch("a{-1}")    # True  <- silently changed meaning

Instead of failing at construction time with a clear error, the term silently becomes something that matches a completely different (and almost certainly unintended) string.

Fix

Add a __post_init__ guard to all four Quantify* dataclasses, mirroring the existing min_count <= max_count check already on QuantifyBetween.

Testing

  • Added regression tests to tests/types/test_dsl.py::test_dsl_init covering all four classes; confirmed they fail without the fix (DID NOT RAISE ValueError) and pass with it.
  • Ran pytest tests/types/ (232 passed) and pre-commit run --files src/outlines/types/dsl.py tests/types/test_dsl.py (mypy + ruff clean).
  • Searched the codebase for other direct construction sites of these four classes — none found; all usage goes through the exactly/at_least/at_most/between factory functions and Term instance methods, which pass through unchanged.

Disclosure: I used an AI coding assistant (Claude) to help identify this bug and draft the fix. I reviewed the diff, wrote/ran the regression tests, and ran the full local test suite + pre-commit before opening this PR.

QuantifyExact, QuantifyMinimum, and QuantifyMaximum accepted negative
counts with no validation, unlike their sibling QuantifyBetween (which
only validated min_count <= max_count, itself missing a non-negative
check). A negative count silently produces a regex quantifier Python's
re engine doesn't recognize (e.g. `{-1}`), which `re` then treats as
literal text instead of raising — so `exactly(-1, "a")` builds a term
that matches the literal string "a{-1}" rather than failing loudly.

Add a __post_init__ guard to all four Quantify* dataclasses, mirroring
the existing min_count/max_count check on QuantifyBetween.

Disclosure: I used an AI coding assistant (Claude) to help identify
this bug and draft the fix; I reviewed the diff, wrote the regression
tests, and ran the full test suite plus pre-commit locally before
opening this PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📚 Documentation preview: https://dottxt-ai.github.io/outlines/pr-preview/pr-1922/

Preview updates automatically with each commit.

@RobinPicard
RobinPicard merged commit e34c6dd into dottxt-ai:main Jul 20, 2026
6 checks passed
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.

2 participants