Skip to content

Fix bug in builtin decidable equality #7032

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

Merged
merged 4 commits into from
Apr 14, 2025

Conversation

ana-pantilie
Copy link
Contributor

@ana-pantilie ana-pantilie commented Apr 10, 2025

@@ -0,0 +1,23 @@
(program
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unrelated to this issue, this golden file was not checked in but it should be.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks - I think this should have been in the Pure PR? It wasn't checked in because it was broken before we decided how to resolve that.

Comment on lines 169 to 171
builtinEq : {A : Set} {{_ : HsEq A}} → Binary.Decidable {A = A} _≡_
builtinEq x y = magicBoolDec (hsEq x y)
builtinEq : {A : Set} → Binary.Decidable {A = A} _≡_
builtinEq {A} x y with primTrustMe {Agda.Primitive.lzero} {A} {x} {y}
... | refl = yes refl
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't this now always return true? primTrustMe just builds an equality proof out of thin air, so will always produce refl? magicBoolDec is about summoning the magical proof from a legitimate boolean answer of true or false, hence it only uses primTrustMe in the true branch. Or am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, the docs suggest this is how you are supposed to use primTrusMe - I think it works on the basis that when Agda sees refl it tries to unify a bunch of stuff, which will then fail if they are incompatible? It looks scary though :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't this now always return true? primTrustMe just builds an equality proof out of thin air, so will always produce refl? magicBoolDec is about summoning the magical proof from a legitimate boolean answer of true or false, hence it only uses primTrustMe in the true branch. Or am I missing something?

magicBoolDec is where the proofs were getting stuck. Why? Because it matches on true and false, which it should get from the hsEq functions. But the hsEq functions cannot ever evaluate to true and false because they're all postulated!

Ok, the docs suggest this is how you are supposed to use primTrusMe - I think it works on the basis that when Agda sees refl it tries to unify a bunch of stuff, which will then fail if they are incompatible? It looks scary though :)

Yes, that's right. primTrustMe will evaluate to refl if it can unify the two terms, if it can't, it will get stuck.

Copy link
Member

Choose a reason for hiding this comment

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

If it always returns yes refl, you may want to reflect in the name of this function that this is a bogus equality check.

Is there no use at all for the original builtEq, which can at least return no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zliu41

If it always returns yes refl

It doesn't always return yes refl. Matching on refl triggers Agda's unification algorithm, which checks whether the two terms are definitionally equal. For example: in builtinEq (mkByteString "foo") (mkByteString "foo") the two terms are structurally equal so unification will succeed, and the function will return yes refl, while builtinEq (mkByteString "foo") (mkByteString "bar") will get stuck because unification does not succeed.

Is there no use at all for the original builtEq, which can at least return no?

The original builtinEq could never return no, it could never return yes either, which was the root of this bug!

I will add some documentation making all of the above more clear.

There is one thing I have realized now: we have only been discussing the behavior of this code at compile time, and we need to also know what happens at runtime. We currently don't have any tests for that, or a way to construct such tests, but I'll add some very soon since this is what I'm working on at the moment.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is what I was wanting to test - if it unifies then it makes refl and returns yes. If it doesn't, it "gets stuck" but I'm not sure what that will look like - it won't make a nice no response I don't think?

Copy link
Contributor

Choose a reason for hiding this comment

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

It just refuses to typecheck - although it produces quite a nice, helpful error! We can probably use this fine in Certificates if we want to certify that the things are equal; we might want to keep the other version for the decision procedures that run in Haskell? They can then do something with the negative response, since hsEq will run there?

Copy link
Member

Choose a reason for hiding this comment

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

Matching on refl triggers Agda's unification algorithm, which checks whether the two terms are definitionally equal.

Ah, that's nice.

we might want to keep the other version for the decision procedures that run in Haskell?

It's probably sufficient to keep hsEq (rather than the previous version of builtinEq)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll need to look into that, I don't think it will be that easy. It's possible to give a different runtime definition to Agda functions (see the warning at https://agda.readthedocs.io/en/stable/language/foreign-function-interface.html#using-haskell-functions-from-agda), but the types need to match. So I would need to implement builtinEq : {A : Set} → Binary.Decidable {A = A} _≡_ in Haskell. In order to do that the types need to have certain pragmas to be identifiable from Haskell, and unfortunately the Agda stdlib does not provide these. I will try to see if some workaround exists. Opened an issue for this: https://github.com/IntersectMBO/plutus-private/issues/1534

@ana-pantilie ana-pantilie marked this pull request as ready for review April 11, 2025 10:16
@ana-pantilie ana-pantilie requested a review from a team April 11, 2025 10:16
Comment on lines 169 to 171
builtinEq : {A : Set} {{_ : HsEq A}} → Binary.Decidable {A = A} _≡_
builtinEq x y = magicBoolDec (hsEq x y)
builtinEq : {A : Set} → Binary.Decidable {A = A} _≡_
builtinEq {A} x y with primTrustMe {Agda.Primitive.lzero} {A} {x} {y}
... | refl = yes refl
Copy link
Member

Choose a reason for hiding this comment

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

If it always returns yes refl, you may want to reflect in the name of this function that this is a bogus equality check.

Is there no use at all for the original builtEq, which can at least return no?

@@ -177,6 +177,7 @@ postulate ByteString : Set

postulate
eqByteString : ByteString → ByteString → Bool
mkByteString : String → ByteString
Copy link
Member

Choose a reason for hiding this comment

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

do you need a COMPILE pragma?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. mkByteString is only used for generating the certificates which are only meant to be type-checked, and not compiled down to Haskell.

@ana-pantilie ana-pantilie enabled auto-merge (squash) April 14, 2025 11:38
@ana-pantilie ana-pantilie merged commit a87b01c into master Apr 14, 2025
6 of 7 checks passed
@ana-pantilie ana-pantilie deleted the ana/fix-certifier-builtin-deceq branch April 14, 2025 12:11
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