Skip to content

Fix issue #841 - #919

Open
jcp19 wants to merge 19 commits into
masterfrom
fix-841
Open

jcp19 wants to merge 19 commits into
masterfrom
fix-841

Conversation

@jcp19

@jcp19 jcp19 commented Apr 22, 2025 •

Copy link
Copy Markdown
Contributor

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 preserves clauses. Only function and method declarations were actually checked against this. Interface method signatures were exempt, so an interface could declare a pure method 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.

@jcp19
jcp19 requested a review from ArquintL April 22, 2025 14:42
@jcp19 jcp19 linked an issue Apr 22, 2025 that may be closed by this pull request

@ArquintL ArquintL left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks overall good to me but I've left some requests for small changes

Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
@jcp19 jcp19 mentioned this pull request Apr 23, 2025
@jcp19
jcp19 marked this pull request as draft June 3, 2025 10:14
jcp19 added 2 commits July 17, 2026 00:23
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.

jcp19 commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor Author

I've rebased this branch on top of that, which changes the picture for most of your comments:

  • case-per-case → case-by-case and the "pure members" error message: those came from this branch's own conditional-measure implementation, which is now superseded by Disallow conditional termination measures in ghost and pure members #1019's version — so both are moot.
  • Redundant if (member.spec.isPure) in wellDefIfPure{Method,Function}: addressed — the pure-signature checks are now factored into a single wellDefIfPureSpec, so isPure is guarded once per call site.
  • Rename wellFoundedIfNeeded / make it self-checking: wellFoundedIfNeeded is now master's version (from Disallow conditional termination measures in ghost and pure members #1019); this branch no longer touches it. The shared helper this branch introduces is wellDefIfPureSpec, used by pure functions, pure methods, and pure interface method signatures.
  • Test restructuring: done — 000841.gobra now splits the cases into separate one-liner interface methods, each triggering exactly one error, and keeps the conditional-measure comment you suggested.

The remaining unique change is the interface part of #841: wellDefIfPureSpec is now also applied to interface method signatures (exactly one result, non-variadic parameters, pure postconditions, no preserves), and pure interface method sigs are required to carry a termination measure — gated behind disableCheckTerminationPureFns, exactly like pure functions/methods.

One thing worth flagging: because the regression suite forces disableCheckTerminationPureFns = true (and the config merge can't turn it back off per-file), the "must have a termination measure" requirement can't be exercised by a regression test — the same limitation that already applies to pure functions. The 000841 test therefore covers the always-checked rules (single result, non-variadic parameters, conditional measures). Happy to revisit if you'd prefer a different approach there.

CI is green. PTAL when you get a chance.


Generated by Claude Code

@jcp19
jcp19 marked this pull request as ready for review July 17, 2026 01:18
@jcp19
jcp19 requested a review from ArquintL July 17, 2026 01:18
@ArquintL

Copy link
Copy Markdown
Member

Note: because the regression-test suite runs with disableCheckTerminationPureFns = true, the "must have a termination measure" case is not exercised by the suite (same as for pure functions); the 000841 test covers the always-checked rules (single result, non-variadic parameters, rejection of conditional measures).

You could add a testcase that uses an in-file config to set disableCheckTerminationPureFns to false or would that not work?

…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.
Comment thread src/main/scala/viper/gobra/frontend/info/implementation/typing/TypeTyping.scala Outdated
Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
Comment thread src/test/resources/regressions/issues/000841.gobra Outdated
Comment on lines +363 to +370
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)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha not quite what I had in mind but I guess it tests the same ^^

@jcp19
jcp19 marked this pull request as draft July 17, 2026 08:53
@jcp19

jcp19 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

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

jcp19 and others added 5 commits July 17, 2026 09:07
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
Comment thread src/test/scala/viper/gobra/GobraCheckTerminationTests.scala
Comment thread src/main/scala/viper/gobra/frontend/ParseTreeTranslator.scala Outdated
Comment thread src/main/scala/viper/gobra/frontend/info/implementation/typing/TypeTyping.scala Outdated
Comment thread src/main/scala/viper/gobra/frontend/info/implementation/typing/TypeTyping.scala Outdated
jcp19 and others added 3 commits September 3, 2026 13:15
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
Comment thread src/test/resources/regressions/features/opaque/opaque-closure-fail1.gobra Outdated
Comment thread src/main/scala/viper/gobra/frontend/ParseTreeTranslator.scala Outdated
@jcp19
jcp19 marked this pull request as ready for review September 4, 2026 07:58
@jcp19
jcp19 requested a review from ArquintL September 4, 2026 07:58
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.

Requiring termination measures for pure interface methods

2 participants