Instance resolution runs too late, leads to `with`-abstraction failure
buggymcbugfix opened this issue · comments
I have a situation where a rewrite
unexpectedly does nothing. How is this possible? I would expect a failed rewrite
to give an error message, otherwise I expect the goal type to change.
I have minimised the proof script to the following:
module Gamma where
open import Relation.Binary.PropositionalEquality
record Semiring : Set₁ where
field
grade : Set
1R : grade
_*_ : grade → grade → grade
leftUnit* : {r : grade} → 1R * r ≡ r
-- [...]
open Semiring {{...}}
foo : {{R : Semiring}} {r : grade} → 1R * r ≡ r
foo {r = r} rewrite leftUnit* {r} = {! !}
Without the rewrite
, the goal is (R Semiring.* Semiring.1R R) r ≡ r
. Adding the rewrite
neither changes the goal, nor gives any error message.
Note: This is easy enough to work around, but I wanted to understand what is going on here.
Addendum
$ agda --version
Agda version 2.6.4.1
I haven't looked at why the rewrite doesn't trigger in this case, but there is no failure for failed rewrites. Rewrite is a special case of with
(see https://agda.readthedocs.io/en/v2.6.4.1/language/with-abstraction.html#rewrite) and doesn't do any checks to see if anything was actually being rewritten.
Thanks for the quick reply. I have tried rewriting to explicit with
per the documentation:
module Gamma where
open import Relation.Binary.PropositionalEquality
record Semiring : Set₁ where
field
grade : Set
1R : grade
_*_ : grade → grade → grade
leftUnit* : {r : grade} → 1R * r ≡ r
-- [...]
open Semiring {{...}}
foo : {{R : Semiring}} {r : grade} → 1R * r ≡ r
foo {r = r} with 1R * r | leftUnit* {r}
... | .r | refl = {! !}
Now Agda complains:
I'm not sure if there should be a case for the constructor refl,
because I get stuck when trying to solve the following unification
problems (inferred index ≟ expected index):
(R Semiring.* Semiring.1R R) r ≟ r
Possible reason why unification failed:
Cannot solve variable r of type Semiring.grade R with solution
(R Semiring.* Semiring.1R R) r because the variable occurs in the
solution, or in the type of one of the variables in the solution.
when checking that the pattern refl has type
(R Semiring.* Semiring.1R R) r ≡ r
I hope I am not spamming you, I did ask a very experienced Agda user before posting here and they were not sure what was going on.
There seems to be a regression here. The above code works fine on Agda 2.6.2.2
Indeed, 2.6.4 is the first that cannot solve the instance arguments in the type signature of foo
, leading to a subsequent unification failure.
Dropping the infix for better readability:
open import Agda.Builtin.Equality
record Semiring : Set₁ where
field
Carrier : Set
one : Carrier
mul : Carrier → Carrier → Carrier
left-unit : (r : Carrier) → mul one r ≡ r
open Semiring {{...}}
foo : {{R : Semiring}} (r : Carrier) → mul one r ≡ r
foo r with mul one r | left-unit r
... | .r | refl = refl
The error is:
I'm not sure if there should be a case for the constructor
refl
,
because I get stuck when trying to solve the following unification problems (inferred index ≟ expected index):Semiring.mul R (Semiring.one R) r ≟ r
Possible reason why unification failed:
Cannot solve variabler
of typeSemiring.Carrier R
with solutionSemiring.mul R (Semiring.one R) r
because the variable occurs in the solution, or in the type of one of the variables in the solution.
when checking that the patternrefl
has typeSemiring.mul R (Semiring.one R) r ≡ r
The explanation is: because instance resolution failed, with
was not able to abstract in the target type, so the type did not become r == r
, so refl
does not match.
@jespercockx : Is this an intended regression or an accidential one?
The changelog for 2.6.4 just says:
agda/doc/release-notes/2.6.4.md
Lines 296 to 299 in c0ceac7
However, it is not clear that this change is responsible, because, interestingly enough, when you turn foo
into a postulate, there are no unsolved constraints. So maybe just instance search kicks in too late now.
I'm sorry, could you point me to which instance argument exactly doesn't get resolved? If I just put the values in a let
-binding rather than a with
, then I don't get any unresolved instances:
foo : {{R : Semiring}} (r : Carrier) → mul one r ≡ r
foo r =
let x = mul one r
eq = left-unit r
in {! !}
So this seems to be a problem with with
rather than with instance search itself, maybe my change just revealed an existing bug?
After some further investigation, the problem seems to be indeed that instance search is called too late. If I add a call to wakeupConstraints_
to the beginning of checkWithRHS
in Agda.TypeChecking.Rules.Def
, then the test case passes.
Now, we should be a bit careful to not call wakeupConstraints_
too often to avoid performance degradations. However, given that with
-abstraction is already an expensive operation (since it fully normalizes the term and the type of the function) maybe having it here is fine. @UlfNorell what do you think?
I'm still not completely sure why it is my change that triggered this bug, since I did not change anything to when instance search is called, and just calling instance search sooner seems to solve the problem.
Don't we run instance search after having checked the type signature?
I mean if constraints can be solved from the type signature alone (as witnessed by turning it into a postulate
), why does this have to have to do anything with with
?
In this case the instance constraint comes from the term that we are doing with
-abstraction on, so it is only created after the type signature is checked.
Ok, maybe I was mislead by the yellow coloring of one
in the type signature. The meta range might be off then.
Appearances can be deceiving. -- Agent Smith
@jespercockx : Should we aim to fix this for 2.6.4.2 or is that too risky?
If the fix is just adding an extra call to wakeupConstraints_
then I do not see how that could cause any regressions. The only possible issue is the negative effect it might have on performance, but since it would only be triggered for with
-abstractions I am not too worried about that.
I will check if there are no issues in the test suite and if not, create a PR.
Hm, actually we already trigger instance search for with-abstracted expressions in some cases:
agda/src/full/Agda/TypeChecking/Rules/Term.hs
Lines 1585 to 1586 in 0b8a99a
See #6868. I'm not really sure what's the rationale here: why do we trigger instance search if we inserted any trailing instance arguments to the with
expression, but not if there are instance constraints arising from the with
-expression itself?
(Also, why are we doing reduce
and instantiateFull
in inferExprForWith
if we will later anyway call normalise
on it? Even worse, the result of reduce
is not even saved!)
Another question: should there be a warning for when you do with
or rewrite
but there are unsolved in the given term? It seems very unlikely that there will be any abstraction happening in that case, so it would perhaps be good to notify the user of that. Of course, with
is also used as a way to just pattern match on a value without doing any abstraction, so perhaps the warning would be annoying too often. (See also the venerable #704.)
@jespercockx wrote:
Even worse, the result of
reduce
is not even saved!
This is often intended so that errors given to the user are not containing needless unfoldings in terms.
The fix for this issue (#7122) does not resolve the regression in the case of rewrite
:
open import Agda.Builtin.Equality
record Semiring : Set₁ where
field
Carrier : Set
one : Carrier
mul : Carrier → Carrier → Carrier
left-unit : (r : Carrier) → mul one r ≡ r
open Semiring {{...}}
foo : {{R : Semiring}} (r : Carrier) → mul one r ≡ r
foo r rewrite left-unit r = refl
Error:
Semiring.mul R (Semiring.one R) r != r of type Semiring.Carrier R
when checking that the expression refl has type
Semiring.mul R (Semiring.one R) r ≡ r
I don't seem to be able to reopen this issue though.
This issue is already reported as fixed in the release notes of 2.6.4.2, so we need a new issue.