feat(justdummies): add JD023-JD024 for scalar chain satisfiability - #375
Merged
Merged
Conversation
The two rules share one walk over a ScalarConstraintState because they read the same state from opposite sides: JD023 asks whether anything remains, JD024 whether anything changed. JD023 reports an integer chain narrowed to nothing — an empty interval, an empty lattice, an emptied allow-list. The library computes this with one emptiness test; the rule runs the same test over the constants written at the call site and stays silent for every argument it cannot fold. JD024 is the only member of the constraint family the run time NEVER reports. Every other contradiction throws eventually and loudly, while an inert constraint leaves the test green exercising a domain the author did not write. The dangerous case is an exclusion of a sentinel the generator could never draw: it misses silently, and starts mattering the day someone widens the range. Info rather than Warning, because a defensive exclusion kept so the intent survives a future widening is a real and reasonable style. Dogfooding caught a genuine bug in the model, not a misread of the library. Any.Int64().LessThanOrEqualTo(long.MinValue) is legal and yields exactly that value, yet the first version declared it empty: the state used -long.MaxValue as its "unbounded" sentinel, which made long.MinValue unrepresentable. The bounds now run to the true extremes, and a bound genuinely beyond the range — GreaterThan(long.MaxValue) — is carried by an explicit saturation flag rather than by arithmetic that would overflow. Four tests pin the extremes in both directions. Scope is deliberately narrow: integer generators only, one expression, every argument folded. A constraint the model has never seen ends the walk rather than being guessed at — a rule claiming a chain is unsatisfiable has to be certain. Rebased onto main after #374, which carried the analyzer library to collection expressions. The two new files were written before that convention landed, so they are brought to it here the same way: `dotnet format style --diagnostics IDE0028`, run to a fixed point, five single initializers, nothing else touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKqyhG9Y4AfdxEYekP2Evq
Reefact
force-pushed
the
claude/justdummies-analyzers-analysis-z9q1xd
branch
from
July 29, 2026 16:26
0031dfb to
0682fef
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two rules sharing one walk over a
ScalarConstraintState, because they read the same state from opposite sides: JD023 asks whether anything remains, JD024 whether anything changed.Type of change
Changes
ScalarChainAdmitsNoValue(Warning) — an integer chain narrowed to nothing: an empty interval, an empty lattice (Between(1, 10).MultipleOf(20)), an emptied allow-list (OneOf(5).Except(5)), or a finite range emptied by its exclusions (Zero().NonZero()).ConstraintWithNoEffect(Info) — a constraint that narrows nothing: an exclusion of a value the domain could never produce, or a bound already implied.ScalarConstraintState— the shared domain model. 26 new analyzer tests (198 total), 4 documentation pages (EN + FR), both index tables, release-tracking rows.JD024 is the only silent one
Every other contradiction in the constraint family throws eventually and loudly. An inert constraint leaves the test green while it exercises a domain the author did not write. The dangerous shape is an exclusion of a sentinel the generator could never draw: it misses silently, and starts mattering the day someone widens the range, at which point the sentinel begins appearing and a distant test starts flaking.
Info rather than Warning, because a defensive exclusion kept so the intent survives a future widening is a real and reasonable style. The rule states the fact without insisting it is a defect.
Dogfooding found a genuine bug in the model
Not a misread of the library this time — a bug in my arithmetic.
Any.Int64().LessThanOrEqualTo(long.MinValue)is legal and yields exactly that value, yet the first version declared it empty: the state used-long.MaxValueas its "unbounded" sentinel to keepMaximum - Minimumfrom overflowing, which madelong.MinValueunrepresentable and condemned a chainAnySignedIntegerTestsasserts is legal.The bounds now run to the true extremes, and a bound genuinely beyond the range —
GreaterThan(long.MaxValue)— is carried by an explicit saturation flag rather than by arithmetic that would overflow. Four tests pin the extremes in both directions.This was also the first wave run under the corrected dogfooding protocol from #373: the
Inforule was escalated to warning before the sweep, not after. JD024 would have been invisible otherwise.Zero hits across
JustDummies.UnitTests,JustDummies.PropertyTests,JustDummies.Xunit.UnitTests,FirstClassErrors.Testing.UnitTestsandFirstClassErrors.UnitTests.Scope, deliberately narrow
Integer generators only, one expression, every argument folded. A constraint the model has never seen ends the walk rather than being guessed at — a rule claiming a chain is unsatisfiable has to be certain.
Testing
dotnet build FirstClassErrors.sln— 0 warnings, 0 errorsdotnet test FirstClassErrors.sln— 2 115 tests, 0 failures across all 13 test projectsFirstClassErrors.Analyzers.UnitTests, 132) — the rules added here live inJustDummies.Analyzers.UnitTests(198)Mutation testing was not run locally.
Documentation
doc/updated —JD023/JD024pages and the rule indexJD023.fr.md,JD024.fr.mdand the French indexArchitecture decisions
Proposed: ADR-____One deviation worth your eye. My original study recommended rejecting a rule for argument-free structural pairs (
Positive()+Negative(),Empty()+NonEmpty()), on the grounds that ADR-0035 assigns structural illegality to the type system. JD023 reportsPositive().Negative()anyway — not as a special case, but because it falls out of the same emptiness computation as everything else.I think that is right: the typed progression ADR-0035 would prescribe is not warranted for a numeric domain (it does not split into fixed shapes), so the choice is analyzer or nothing. But JD016 already did the same for
Empty().NonEmpty()without my flagging it, and I would rather say so than let the pattern spread silently. If you read ADR-0035 as forbidding it, both are oneifaway from being excluded.Remaining
Four rules I can build alone — duplicate pool values, the empty relative URI, the unused
Combineoperand,Distinctover reference equality — which will follow as the Composition wave. Then the two that need your decision: the unsupported-regex rule (grammar shared with the library) and the production-code rule (IsTestProjectoracle).🤖 Generated with Claude Code
https://claude.ai/code/session_01GKqyhG9Y4AfdxEYekP2Evq
Generated by Claude Code