A field keeps what its type said - #969
Merged
Merged
Conversation
A parameter of type `Positive` was already known to be positive. A field
declared `Positive` was not, and the two say the same thing.
record Held { by: Positive }
fn needs(n: Positive) -> Int where n > 0, { n }
fn through(h: Held) -> Int { needs(h.by) } // was Guarded, is Proven
The value has no name at that call for anything to have narrowed, and no
contract answered for it, so the only thing that knew anything was the type
the record was declared with. Nothing asked it. The comment in `facts.rs` said
so in as many words: "every other field read is a value nothing here knows".
`Env` can ask now. The checker answers out of its type table, because the fact
machinery has none, and `Range::ANY` when the type says nothing.
Measured before writing any of it, three ways, to find where a refinement
stops travelling rather than guessing:
needs(b) where b: Positive proven
needs(h.by) where by: Positive guarded <- this
needs(b) where b: NonZero guarded <- something else
The third is a different limit and is left alone: `value != 0` admits
everything but one number, which is two intervals and not one, so `Facts`
cannot hold it. That is what still stops `std/ratio` from proving
`simplified`'s `bottom != 0` at its call sites, along with the product of two
numbers, which no interval bounds.
No obligation in the corpus moves, and the changelog says so: the records there
are declared with plain `Int`. What moved is that the compiler stopped losing
something it already had.
Break-verified. The first attempt reported both cases survived and was wrong
both times -- the script never applied the edit -- so it checks that the text
changed and that the run produced a result before reading it. Then: taking the
question away is caught by name. The second case really did survive, and it was
a redundant guard rather than a missing test: a type that is not refined
already answers `Range::ANY`, so asking twice bought nothing. The guard and the
`Option` around it are gone.
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.
A parameter of type
Positivewas already known to be positive. A fielddeclared
Positivewas not, and the two say the same thing.The value has no name at that call for anything to have narrowed, and no
contract answered for it, so the only thing that knew anything was the type the
record was declared with — and nothing asked it.
facts.rssaid so itself:Measured first
Where a refinement stops travelling, found by walking it rather than guessed:
needs(b)whereb: Positiveneeds(h.by)whereby: Positiveneeds(b)whereb: NonZeroTwo separate gaps, and only the first is closed here.
The second is
value != 0, which admits everything but one number: twointervals, not one, and
Factskeeps ranges. That is a limit of what a factis, not of fields, and it is one of the two things still stopping
std/ratiofrom proving
simplified'sbottom != 0at its call sites — the other beingthe product of two numbers, which no interval bounds. Both are stated in the
changelog rather than left to be rediscovered.
The change
facts::Envgains one question: what does this expression's declared typeadmit. The checker answers from its type table, because the fact machinery has
none, and answers
Range::ANYwhen the type says nothing. Reading arefinement's own predicate stays blind about fields, or a predicate mentioning
one would come straight back for the type being read.
Honest about what it buys
No obligation in the corpus moves. The records there are declared with
plain
Int, so nothing there was losing anything. What moved is that thecompiler stopped throwing away something it already had, and the corpus numbers
(167 / 11 / 23) are unchanged on purpose rather than by accident.
Break-verified
The survivor was not a missing test. A type that is not refined already answers
Range::ANY, so the guard asking whether it was refined bought nothing — deadcode, and it and the
Optionaround it are gone.The first run of that script reported both cases survived, and was wrong
both times: the edit never applied, and a check that cannot see its own
mutation is worse than no check. It verifies the text changed and that the run
produced a result before reading either.
Checks
cargo fmt,clippy -D warnings,nextest --profile ci(2469 passed, 4skipped),
cargo test --doc --workspace.