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

Fixes for typeclasses #3134

Merged
merged 30 commits into from
Dec 5, 2023
Merged

Fixes for typeclasses #3134

merged 30 commits into from
Dec 5, 2023

Conversation

mtzguido
Copy link
Member

@mtzguido mtzguido commented Dec 3, 2023

Most importantly, trying to defer the execution of meta-args until their environment and goal are fully defined. This reduces the order dependency observed in #3130, but it's not completely eliminated. In particular, we F* can still try to run a meta-arg in a partially-unresolved context, but maybe we should remove that possibility altogether.

If we happen to unify the implicits for two typeclass constraints
(likely the same one), we need to retain the tag so this has
any chance of being solved. This changes makes the unifier
retain the meta tag, with a preference for the left one, which
is of course totally arbitrary.
I thought this was needed for backwards-compatibility, but apparently
not. The few examples that break are ambiguous, and having this
restriction in place seems like the right thing to do and brings some
peace of mind.
@mtzguido
Copy link
Member Author

mtzguido commented Dec 4, 2023

but maybe we should remove that possibility altogether.

I've now done this, turns out everything works fine without it given the other changes! This means we are essentially forcing typeclasses (meta args in general) to run in topological order of dependencies, and we will never attempt a problem that is not fully defined, so we should not see anything unexpected.

This doesn't solve #2602 though, @@resolve_implicits is a different beast.

@TWal
Copy link
Contributor

TWal commented Dec 4, 2023

This one fails now. It's a bit strange, it doesn't use a fancy typeclass dependency.

class ord_leq (a:eqtype) =
  { leq: a -> a -> bool
  ; trans: (x:a) -> (y:a) -> (z:a) -> Lemma (x `leq` y /\ y `leq` z ==> x `leq` z)
  }

let transitivity {| ord_leq 'a |} (x y z : 'a)
  : Lemma (x `leq` y /\ y `leq` z ==> x `leq` z)
  [SMTPat (x `leq` y); SMTPat (x `leq` z)]
  = trans x y z

let transitivity_forall #a {| ord_leq a |} unit
  : Lemma (forall (x y z : a). x `leq` y /\ y `leq` z ==> x `leq` z )
= ()

@TWal
Copy link
Contributor

TWal commented Dec 4, 2023

(looks like typeclass resolution is bamboozled by the unit, which is misleading: I guess the intent was for it to be (), but it is in fact a variable named unit of unknown type ; removing this extra argument or replacing it with () works fine.)

@mtzguido
Copy link
Member Author

mtzguido commented Dec 4, 2023

Ah, yes, the typeclass tactic will now not run in contexts with unresolved types in them, so this unit:_ is to blame. But the error is pretty bad, and could also expect for the unit:_ to be generalized into unit:b for some fresh b, and only then try to solve the constraints.

I'll try to at least make it clear that the tcresolve tactic is not running.

@TWal
Copy link
Contributor

TWal commented Dec 4, 2023

I agree, the code looks a bit fishy and should probably be fixed! Although in that case I would indeed expect to have unit:b for some fresh b.

Here is something else that works with master but not this PR (from Comparse tests):

class bytes_like (bytes:Type0) = {
  x:unit;
}

assume val t: bytes:Type0 -> {|bytes_like bytes|} -> a:Type -> Type

irreducible let with_parser (#bytes:Type0) {|bytes_like bytes|} (#a:Type0) (x:t bytes a) = ()

assume val test_dep_nat_e: bytes:Type0 -> {|bytes_like bytes|} -> nat -> Type0

assume val ps_test_dep_nat_e: #bytes:Type0 -> {|bytes_like bytes|} -> n:nat -> t bytes (test_dep_nat_e bytes n)

assume val ps_option: #bytes:Type0 -> {|bytes_like bytes|} -> #a:Type -> t bytes a -> t bytes (option a)

noeq type test_with_parser (bytes:Type0) {|bytes_like bytes|} = {
  [@@@ with_parser #bytes (ps_option (ps_test_dep_nat_e 256))]
  f2: option (test_dep_nat_e bytes 256);
  [@@@ with_parser #bytes (ps_option (ps_test_dep_nat_e 256))]
  f3: option (test_dep_nat_e bytes 256);
}

Trying to force it would make typeclass instantiation run now,
when there are possibly yet-unresolved variables in the environment,
which would prevent the tactic from running. See the Bug3130f example.

This patch makes the typechecker just accumulate the guard, and force it
at the end, where the free uvars restriction will cause the constraints
to be solved in order.
@mtzguido
Copy link
Member Author

mtzguido commented Dec 4, 2023

I updated the branch. The example with the extra unit:_ argument now works by generalizing it before typeclass resolution.

The other snippet also works after a fix to the checking of binder attributes.

I have one more tiny regression, which I'm still looking at.

Leave that to the rest of the typechecker.
We had previously made the choise to run meta argument tactics in the
environment in which they were introduced, instead of the environment of
the uvar that the meta is attached to. These two can differ if the uvar
is unified with other uvars, possibly in more constrained environments.

But, this leads to all sorts of weirdness, such as getting trying
to solve a uvar in an environment that contains itself, if we have
something like:

   let v : ty ?u1 = ... in
   let w : ty ?u2 = ... in
   (* something that unifies the type of v and w *)

We may have set ?u2:=?u1 and copied the meta over, so when we try to
run it to solve ?u1, we should not have ?u1 in scope.
@mtzguido
Copy link
Member Author

mtzguido commented Dec 5, 2023

I think everything should work now, still with the restriction of not running meta args in any open context/type. Turns out we did rely on it in one place (FStarLang/steel#127) but I think we can just set a compat option for now.

Maybe the right thing to do is add a marker to the tactic specifying it "accepts" being called in unresolved contexts. We could even do that for typeclasses in order to introduce functional dependencies, allowing tcresolve and the unifier to interoperate in a single common loop. But I think this PR can go in already.

@TWal
Copy link
Contributor

TWal commented Dec 5, 2023

Hmm, this files still fails with me: https://github.com/TWal/comparse/blob/main/src/Comparse.Bytes.Typeclass.fsti .
It seemed like you added it in F* tests in commit 8e9f09a but I don't see it anymore, did you remove it?

@mtzguido
Copy link
Member Author

mtzguido commented Dec 5, 2023

It's still there, just in my fork mtzguido@8e9f09a. And I just downloaded that file and it passes for me.

Are you sure you're on bbbcff1? I did have to bump the checked file version, so maybe a make clean would be needed...

@TWal
Copy link
Contributor

TWal commented Dec 5, 2023

Hmm, sorry I did the test between two meetings and completely messed-up my git-fu.
Everything works well for me!

@nikswamy
Copy link
Collaborator

nikswamy commented Dec 5, 2023

I just had a look through it. It look good to me.

Note to self. I see the main semantic changes are:

  • Rel.fst: is should_run_meta_arg to block unless the ctx uvars are empty; and to skip solving such main cases in the main resolve_implicits loop, unless the compat flag is set.
  • The change in generalize to skip past variables that the meta attribute set

Also, great to see the more widespread use of show etc. throughout.

Thanks!

@mtzguido mtzguido merged commit 8dfeb9b into FStarLang:master Dec 5, 2023
2 checks passed
@mtzguido mtzguido deleted the 3130 branch December 5, 2023 20:27
@mtzguido
Copy link
Member Author

mtzguido commented Dec 6, 2023

Everything works well for me!

Awesome! Thanks for the all the examples, they were super helpful.

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.

3 participants