Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Equality support inferred for nullable iterator but not non-null iterator #3273

Closed
RustanLeino opened this issue Dec 27, 2022 · 0 comments · Fixed by #3284
Closed

Equality support inferred for nullable iterator but not non-null iterator #3273

RustanLeino opened this issue Dec 27, 2022 · 0 comments · Fixed by #3284
Assignees
Labels
kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking

Comments

@RustanLeino
Copy link
Collaborator

RustanLeino commented Dec 27, 2022

Dafny version

3.10.0

Code to produce this issue

The iterator declaration

iterator Iter<Y>(u: set<Y>) // this infers Y to be (==)

gives rise to two types, the nullable type Iter? and the non-null type Iter. From the signature of the iterator declaration, Dafny infers the (==) characteristic for type parameter Y (since the signature includes the type set<Y>).

Therefore, if D is a type that does not support equality, then neither the type Iter?<D> nor the type Iter<D>. Alas, Dafny reports an error only for Iter?<D>, not for Iter<D>.

Diagnosis: While Dafny infers (==) for Y for the type Iter?, it does not apply the same learning to type Iter.

Repro:

iterator Iter<Y>(u: set<Y>) // this infers Y to be (==)

method Repro<K>() {
  var iter: Iter<K>; // should be an error, because K is not (==)
}

Command to run and resulting output

$ dafny test.dfy

Dafny program verifier finished with 1 verified, 0 errors

What happened?

This is what is expected for the 8 cases of inferred/declared (==), passing in equality-support type or not, and using the nullable/non-null version of the iterator type:

iterator IterA<X(==)>() // this declares X to be (==)

iterator IterB<Y>(u: set<Y>) // this infers Y to be (==)

method Test<K, L(==)>() {
  var a0: IterA<K>; // error: K is not (==)
  var a1: IterA<L>;
  var b0: IterB<K>; // error: K is not (==)
  var b1: IterB<L>;

  var c0: IterA?<K>; // error: K is not (==)
  var c1: IterA?<L>;
  var d0: IterB?<K>; // error: K is not (==)
  var d1: IterB?<L>;
}

The issue does not appear to affect soundness. The following example is close, but no cigar:

iterator EqIter<Y>(u: set<Y>, x: Y, y: Y) yields (eq: bool)
  yield ensures eq == (x == y)
  ensures false
{
  while true {
    yield x == y;
  }
}

datatype Dt = Dt(a: int, ghost b: int)

method Main() {
  var d0 := Dt(0, 0);
  var d1 := Dt(0, 1);
  assert d0 != d1; // clearly

  // The following ought to be an error, because EqIter should take a (==) type argument.
  var iter: EqIter<Dt>;
  // The following does give an error, since the constructor lives in class EqIter whose
  // type parameer has been inferred to be (==).
  iter := new EqIter({}, d0, d1); // error:
  var more := iter.MoveNext();
  assert more;
  assert iter.eq == (d0 == d1) == false; // according to the yield-ensures clause
  print iter.eq, "\n";
  if iter.eq {
    var ugh := 1 / 0; // according to the verifier, we never get here
  }
}

What type of operating system are you experiencing the problem on?

Mac

@RustanLeino RustanLeino added kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking labels Dec 27, 2022
@RustanLeino RustanLeino self-assigned this Dec 27, 2022
RustanLeino added a commit to RustanLeino/dafny that referenced this issue Dec 28, 2022
RustanLeino added a commit that referenced this issue Dec 29, 2022
This PR cleans up the AST traversals that occur during "pass 0" and "pass 1" of the resolver.
These now do:

* Pass 0
    - resolve names and infer types
    - check that type inference did not leave any types underspecified
- perform substitution for DefaultValueExpression, compute .ResolvedOp,
and some similar checks and AST updates as necessary to make the AST
coherent
* Pass 1
    - discover bounds
    - compute call graph
    - compute and check ghost things
    - determine tail recursion

Each "pass" really consists of several traversals of the AST. So,
"phase" or something like that would be a better name.

With this PR, the dependencies between pass 0 and pass 1 have been
sorted out, and so have the dependencies among the various things that
are happening in pass 1.

Pass 0 is the most delicate one, because it starts with an AST about
which very little is known. The general direction of the work in this PR
is to try to make later traversals be as independent of each other as
possible.

A few awkward dependencies remain between the passes. The one that comes
to mind is that the body of a prefix predicate/lemma cannot be filled
until the call-graph has been built. For this reason,
`CheckTypeInference` is run on these prefix bodies once they have been
constructed. It's possible that this could be improved by making sure
that the prefix bodies are created without a need to call
`CheckTypeInference` on them at all.

This PR also fixes some minor things:
* pretty-printing of `newtype` declarations
* scope and two-state checks for attributes
* inferred equality support for non-null iterators (issue #3273)

Fixes #3273

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
RustanLeino added a commit to RustanLeino/dafny that referenced this issue Dec 29, 2022
This PR cleans up the AST traversals that occur during "pass 0" and "pass 1" of the resolver.
These now do:

* Pass 0
    - resolve names and infer types
    - check that type inference did not leave any types underspecified
- perform substitution for DefaultValueExpression, compute .ResolvedOp,
and some similar checks and AST updates as necessary to make the AST
coherent
* Pass 1
    - discover bounds
    - compute call graph
    - compute and check ghost things
    - determine tail recursion

Each "pass" really consists of several traversals of the AST. So,
"phase" or something like that would be a better name.

With this PR, the dependencies between pass 0 and pass 1 have been
sorted out, and so have the dependencies among the various things that
are happening in pass 1.

Pass 0 is the most delicate one, because it starts with an AST about
which very little is known. The general direction of the work in this PR
is to try to make later traversals be as independent of each other as
possible.

A few awkward dependencies remain between the passes. The one that comes
to mind is that the body of a prefix predicate/lemma cannot be filled
until the call-graph has been built. For this reason,
`CheckTypeInference` is run on these prefix bodies once they have been
constructed. It's possible that this could be improved by making sure
that the prefix bodies are created without a need to call
`CheckTypeInference` on them at all.

This PR also fixes some minor things:
* pretty-printing of `newtype` declarations
* scope and two-state checks for attributes
* inferred equality support for non-null iterators (issue dafny-lang#3273)

Fixes dafny-lang#3273

<small>By submitting this pull request, I confirm that my contribution
is made under the terms of the [MIT
license](https://github.com/dafny-lang/dafny/blob/master/LICENSE.txt).</small>
# Conflicts:
#	Source/DafnyCore/Resolver/Resolver.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: bug Crashes, unsoundness, incorrect output, etc. If possible, add a `part:` label part: resolver Resolution and typechecking
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant