Repository navigation
Conversation
This reverts commit 0b571fe.
ArquintL
left a comment
There was a problem hiding this comment.
Looks overall good to me but I've left some requests for small changes
Rebase the fix for issue #841 onto master, whose PR #1019 already introduced the general rejection of conditional termination measures for ghost and pure members (including interface method signatures). The remaining, still-open part of issue #841 is that pure interface method signatures were not required to have termination measures and their signatures were not validated. This resolves the merge by: - Extracting the signature-level pure checks into a shared 'wellDefIfPureSpec', now used by pure functions, pure methods, and pure interface method sigs. - Requiring pure interface method sigs to have a termination measure. - Keeping master's conditional/wildcard-measure and mayInit handling. - Adding 'decreases' to the pure ByteOrder interface methods in the stdlib stub. - Restructuring the regression test so each case triggers exactly one error.
…ddedInterfaces-fail3 The requirement that pure members carry a termination measure is gated behind 'disableCheckTerminationPureFns', which the regression test suite disables, so that case cannot be exercised here. The test now covers the checks that always apply to pure interface method signatures: exactly one result, non-variadic parameters, and rejection of conditional termination measures. embeddedInterfaces-fail3 declared 'pure g()' with no result, which is now rejected at the interface itself (issue #841). Give it a result so the test keeps exercising its intended error: a non-pure method implementing a pure interface method.
|
I've rebased this branch on top of that, which changes the picture for most of your comments:
The remaining unique change is the interface part of #841: One thing worth flagging: because the regression suite forces CI is green. PTAL when you get a chance. Generated by Claude Code |
You could add a testcase that uses an in-file config to set |
…ace methods The regression-test suite runs with disableCheckTerminationPureFns = true, so the requirement that a pure interface method carry a termination measure cannot be exercised there. The type-checking unit tests, however, use the default Config() in which the check is enabled, so we add two tests: a pure interface method without a termination measure is rejected, and one with a measure is accepted. Addresses ArquintL's review comment on issue #841.
| test("Typing: a pure interface method without a termination measure is not well-defined") { | ||
| assert (!frontend.isWellDef(pureInterfaceType(Vector.empty)).valid) | ||
| } | ||
|
|
||
| test("Typing: a pure interface method with a termination measure is well-defined") { | ||
| val measure = PTupleTerminationMeasure(Vector.empty, None) | ||
| assert (frontend.isWellDef(pureInterfaceType(Vector(measure))).valid) | ||
| } |
There was a problem hiding this comment.
haha not quite what I had in mind but I guess it tests the same ^^
|
Oops, I saw that Claude went ahead and commented on this thread and marked this for review without me expecting or requesting any of these actions. Sorry that this led you to spend your time on something that was not ready for review @ArquintL |
Address review feedback on the scattered measure checks for pure interface methods: - wellDefIfPureSpec now validates the termination measure of every pure member (function, method, and interface method signature): it must be present (unless disableCheckTerminationPureFns) and non-conditional. It also asserts its precondition that the spec is pure. - wellFoundedIfNeeded / noConditionalMeasureIfGhostOrPure are renamed to wellFoundedIfGhost / noConditionalMeasureIfGhost and restricted to ghost (non-pure) members, since pure members are now handled uniformly; this avoids double-reporting. - The interface handling in wellDefType delegates pure-signature checks entirely to wellDefIfPureSpec and only rejects conditional measures for ghost (non-pure) signatures; wildcard measures remain rejected for all interface signatures. - Restructure the 000841 regression test into one-liner method signatures. Removes the TypeTypingUnitTests cases added earlier: the anonymous-interface harness does not exercise interface method-signature checks, so they did not test the intended behaviour.
'decreases pure M1(a int)' does not parse: the decreases clause consumes the following tokens as the measure and rejects the 'pure' keyword. Put 'decreases' on its own line; 'pure' directly before the method name parses fine.
Adapt the branch to the termination-checking helpers that master gained in the meantime (#1019, #983): - `wellFoundedIfGhost` (previously `wellFoundedIfNeeded`) and the rejection of conditional measures are merged into a single check that applies to ghost (non-pure) functions and methods only. Pure members, ghost or not, are checked uniformly in `wellDefIfPureSpec` instead, which avoids reporting the same problem twice. - `wellDefIfPureSpec` now also validates the termination measure of every pure member, so that pure functions, pure methods and pure interface method signatures are held to exactly the same requirements. It asserts its precondition that the given specification is pure. - The interface case of `wellDefType` keeps master's `interfaceMethodsNotAtomic` and wildcard-measure checks, and delegates the checks of pure method signatures to `wellDefIfPureSpec`. - `visitMethodSpec` positions the specification of an interface method signature at the `specification` context, just like `visitChildren` does for function and method declarations. Without a position, errors reported on such a spec (e.g., a missing termination measure) cannot be rendered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
The main regression test suite runs with `disableCheckTerminationPureFns` set, so it cannot exercise the requirement that a pure member carries a termination measure. An in-file configuration cannot opt out of that setting either, since boolean flags are merged by disjunction. Add `GobraCheckTerminationTests`, which runs the files in `src/test/resources/check_termination` with the flag disabled, and a test file covering pure functions, pure methods, ghost functions and, following issue #841, pure interface method signatures. Extend the `000841` regression test to the requirements on pure method signatures that are checked regardless of that flag: exactly one result parameter, non-variadic parameters, no `preserves` clauses and pure postconditions. Conditional measures on pure interface methods are already covered by `features/termination/conditional-measure-ghost-pure-fail.gobra`. Also restore the commented-out body of `binary.Size`, which was dropped by an unrelated edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
Ghost functions and methods must be guaranteed to terminate, but the method signatures declared in an interface were exempt from that requirement, the same inconsistency that issue #841 reports for `pure`. Ghost method signatures are now held to the requirement too. No ghost signature in the built-in definitions or in the stdlib stubs is affected, as all of them already carry a measure. Move the check itself into `TerminationTyping`, as `mustTerminateErrors`, so that the requirement is expressed once and every member that must terminate is checked against the same rule: ghost functions and methods, ghost interface method signatures, and, via `wellDefIfPureSpec`, all pure members. This replaces the copies of the rule that lived in `wellFoundedIfGhost` and in the interface case of `wellDefType`, and gives all of them a single error message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
Address the review feedback that the same checks are spread over too many places: - Pull `args` up into `PCodeRootWithResult` (all of its implementors already declare it) and introduce `PCodeRootWithSpec` for the code roots that carry a specification of their own. `wellDefPureSpec` (previously `wellDefIfPureSpec`) then takes the member alone, instead of taking the member, its arguments and its specification as three separate parameters. - Introduce `wellDefMethodSig`, the counterpart of `wellDefActualMember` for the members declared by an interface. The interface case of `wellDefType` now maps it over the signatures instead of iterating over them four times, once per check. - Move the rejection of wildcard measures in interface methods to `TerminationTyping`, next to the other rules about termination measures. Position the specification in `visitSpecification` rather than at the call site in `visitMethodSpec`. This is idempotent with what `visitChildren` does for all other specifications, and it is a bug fix: the specification of an interface method signature was the only `PFunctionSpec` in the AST without a position, so the well-definedness checks that `wellDefSpec` reports on it crashed the type checker with a `NoSuchElementException`. The new `features/termination/interface-sig-measures-fail.gobra` covers those checks. Also test that conditional termination measures are rejected on ghost and pure interface methods. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
Pure closure literals were the one kind of pure member that no well-definedness
check reached. As a result, Gobra silently accepted a closure with variadic
parameters, a `preserves` clause, an impure postcondition or no termination
measure, and it crashed while desugaring a pure closure whose body is not a
single return of a pure expression:
cl := pure func g() (x int, y int) { return 1, 2 }
Logic error: unexpected pure function body: Vector(return 1, 2)
`wellDefIfPureClosure` now applies `wellDefPureSpec` to them, exactly as
`wellDefIfPureFunction` and `wellDefIfPureMethod` do for the corresponding
declarations, so all of these are reported as type errors instead.
The closure in `features/opaque/opaque-closure-fail1.gobra` had a `preserves`
clause and a body that assigns before returning, neither of which is allowed in a
pure member. Give it a well-formed body and precondition, so that the file keeps
testing what it is about, namely that closures cannot be made opaque.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
Record why `GobraCheckTerminationTests` exists and what belongs in it, and point at it from the place where the main regression test suite disables the check, so that the next reader does not have to rediscover that an in-file configuration cannot opt out of that setting. Likewise, record in `mustTerminateErrors` why it does not test `!measuresGuaranteeTermination`: the two reject exactly the same specifications, and the difference is only a second, vaguer message for a specification whose measures are all conditional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SBDq9vf2yu39GBS6iWDFfx
…/TerminationTyping.scala
…/TerminationTyping.scala
Fixes #841: every pure and ghost member is now held to the same requirements, whichever way it is declared.
Gobra requires ghost and pure members to be guaranteed to terminate, and requires pure members to have exactly one result, non-variadic parameters, pure postconditions and no
preservesclauses. Only function and method declarations were actually checked against this. Interface method signatures were exempt, so an interface could declare apuremethod with no termination measure or no result at all, which no implementation can satisfy. Pure closure literals were exempt too: Gobra accepted them silently and then crashed while desugaring.