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

Polymorphic equality #1267

Open
wants to merge 50 commits into
base: dev
Choose a base branch
from
Open

Polymorphic equality #1267

wants to merge 50 commits into from

Conversation

GuoDCZ
Copy link

@GuoDCZ GuoDCZ commented Apr 15, 2024

#1252.

Enable polymorphic equal / not equal comparison for:

  • Int, Bool, Float, String: Compare value
  • Tuple & List: Pairwise comparison. For List, also check internal type consistency
  • Sum: Same as Ocaml
    We disallow comparison of inconsistent types and types contain arrow:
  • Dynamic / Static raised Inconsistent when inconsistent types of both side is detected.
  • Dynamic / Static raised CompareArrow when either a primitive is an arrow or a compound type contains an arrow
    • A Sum type raise CompareArrow if any entry contains an arrow argument.

Front-end quick tests:

test 1 == 2 end;
test 1. == 1. end;
test false!= false end;
test "str" == "str" end;
test [1,2,3]!= [2,1] end;
test (false, "") == (false, "") end;
type T = +A+B(Int) in
test A == B(1) end;
test A == A end;
test B(2) == B(1) end;
#Static Error: type inconsistency#
1 == 1.;
"" == false;
("1", false) == (1, true);
[1.] == [1];
[1.,  ] == [ , 1];
#No Static Error: hole#
(1,  ) == ( , 2.);
[1,  ] == [ ];
#Internal Inconsistency#
[1, false] == [1, false];
type T1 = +B+C in
A == C;
#Static Error: type contains arrow#
string_compare == string_compare;
let f = fun x -> x in
f == f;
let a = (1, [f]) in
a == a; 
let a : +A+B(Int->Int) =    in
a == a;
let a : rec R -> A(Int->Int) + R =    in
a == a;
let a = typfun A -> fun x : A -> x in
a == a;
type T2 = +A(Int->Int)+B in
B == B;
#Hidden inconsistency caught in dynamic#
let x: ? = 1 in 
let y: ? = 1. in
test x == y end;
#Hidden containing arrow caught in dynamic#
let f: ? = fun x -> x in
test f == f end;
let b1 : ? = B in
test b1 == b1 end; 

@GuoDCZ GuoDCZ added the in-development for PRs that remain in development label Apr 15, 2024
@GuoDCZ GuoDCZ self-assigned this Apr 15, 2024
@GuoDCZ GuoDCZ marked this pull request as ready for review April 18, 2024 21:06
@cyrus- cyrus- changed the title Polymorphic equal test Polymorphic equality Apr 24, 2024
@cyrus-
Copy link
Member

cyrus- commented Apr 25, 2024

@GuoDCZ let's better handle functions:

  • there should be a static error on an equivalence where either side has arrow type
  • there should be a dynamic error if at run-time, either side evaluates to a function or cast to arrow type

@cyrus- cyrus- marked this pull request as draft April 25, 2024 20:24
@GuoDCZ
Copy link
Author

GuoDCZ commented Apr 28, 2024

@cyrus- just to make sure I got the idea.

When we refer to static error, does it mean something like:
image
If so, I don't think Info.t.error_inconsistent fit our situation. I am thinking about introducing a new synthesis mode Mode.t.SynEqual, who raises a new error Info.t.error_unexpectedArrow when finding an arrow type. Because it may influence existing code base, IDK if it's OK to do so.

@cyrus-
Copy link
Member

cyrus- commented Apr 29, 2024

Yes. I'm not sure you need a new synthesis mode, but a new error definitely makes sense.

@GuoDCZ GuoDCZ marked this pull request as ready for review May 28, 2024 13:15
@Negabinary
Copy link
Contributor

I think equality with holes should probably not evaluate - e.g. 2 == ? should stay as 2 == ? rather than evaluate with the unknown, because it's possible the unknown will eventually be filled with the number 2.

@cyrus-
Copy link
Member

cyrus- commented Jul 25, 2024

@GuoDCZ the recent merge of #1121 modified elaboration to include the type in the elaborated constructor, so can you update this branch to make use of that to address the issues you identified above? marking as draft until then.

@cyrus- cyrus- marked this pull request as draft July 25, 2024 18:03
@GuoDCZ
Copy link
Author

GuoDCZ commented Aug 13, 2024

Remaining type inconsistency checker issue. Can be solved by fixing #1370

@cyrus-
Copy link
Member

cyrus- commented Mar 2, 2025

@GuoDCZ is this ready to review? if so go ahead and click that button

@GuoDCZ GuoDCZ marked this pull request as ready for review March 2, 2025 02:47
Copy link

codecov bot commented Mar 2, 2025

Codecov Report

Attention: Patch coverage is 8.82353% with 217 lines in your changes missing coverage. Please review.

Project coverage is 52.37%. Comparing base (6df1774) to head (bc1c7c5).

Files with missing lines Patch % Lines
src/haz3lcore/dynamics/DHExp.re 0.00% 163 Missing ⚠️
src/haz3lcore/lang/term/Typ.re 14.28% 18 Missing ⚠️
src/haz3lcore/dynamics/Transition.re 0.00% 17 Missing ⚠️
src/haz3lcore/dynamics/InvalidOperationError.re 0.00% 5 Missing ⚠️
src/haz3lcore/statics/Elaborator.re 50.00% 5 Missing ⚠️
src/haz3lcore/statics/Self.re 44.44% 5 Missing ⚠️
src/haz3lcore/statics/Info.re 57.14% 3 Missing ⚠️
src/haz3lcore/statics/Statics.re 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1267      +/-   ##
==========================================
- Coverage   53.25%   52.37%   -0.88%     
==========================================
  Files         103      103              
  Lines       10583    10814     +231     
==========================================
+ Hits         5636     5664      +28     
- Misses       4947     5150     +203     
Files with missing lines Coverage Δ
src/haz3lcore/statics/Statics.re 74.09% <83.33%> (+0.08%) ⬆️
src/haz3lcore/statics/Info.re 28.16% <57.14%> (+0.57%) ⬆️
src/haz3lcore/dynamics/InvalidOperationError.re 6.25% <0.00%> (-2.09%) ⬇️
src/haz3lcore/statics/Elaborator.re 64.47% <50.00%> (-1.62%) ⬇️
src/haz3lcore/statics/Self.re 30.76% <44.44%> (+1.60%) ⬆️
src/haz3lcore/dynamics/Transition.re 29.66% <0.00%> (-1.09%) ⬇️
src/haz3lcore/lang/term/Typ.re 53.37% <14.28%> (-1.71%) ⬇️
src/haz3lcore/dynamics/DHExp.re 10.78% <0.00%> (-22.55%) ⬇️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cyrus-
Copy link
Member

cyrus- commented Mar 3, 2025

@GuoDCZ can you add some unit tests for this functionality

Copy link
Contributor

@Negabinary Negabinary left a comment

Choose a reason for hiding this comment

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

Playing around I found a statics bug:

(5 == 3.0): doesn't have an error, when I think it should

image

Likewise this message is slightly strange

image

Also to echo Cyrus' comment, iirc you put a lot of effort in to dealing with edge cases (playing around with it now it does feel pretty robust to weird sum constructor/cast situations) it would be helpful if you could add some tests to stop anyone else from later breaking those edge cases.

| Match(_)
| Dot(_)
| Cast(_) => false
| Fun(_)
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a comment explaining why fun is comparable? - or if I'm understanding it right, maybe this function could be named something like is_value?

@@ -529,6 +529,44 @@ module Transition = (EV: EV_MODE) => {
kind: BinBoolOp(Or),
is_value: false,
});
| BinOp(Int((Equals | NotEquals) as op), d1, d2) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder whether we should rename BinOp(Int(Equals)) to BinOp(Equals) or BinOp(Poly(Equals))

let expr: t =
if (!DHExp.ty_consistent(d1', d2')) {
DynamicErrorHole(
BinOp(Int(op), d1', d2') |> rewrap,
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a reason we need to put fully-cast-stripped expressions here? It might be nice if we could keep the inner casts on output.

@GuoDCZ
Copy link
Author

GuoDCZ commented Mar 8, 2025

I am trying to add a test case. But I get:

 Received: `{ term =
  (BinOp ((Int Equals), { term = EmptyHole; annotation = None },
     { term = EmptyHole; annotation = None }));
  annotation = None }'

where an error of comparing arrow is expected. I am sure this is a static error tested from frontend. Having no idea why It does not appear in testing.

      test_case("Polymorphic equal compare arrow 2", `Quick, () => {
        annotated_tree_test(
          {|let a : +A+B(Int->Int) = in (a == a)|},
          FIError.(
            Exp.(
              bin_op(
                Int(Equals),
                empty_hole(),
                empty_hole(),
                ~ann=
                  Some(
                    FTemp.Typ.(
                      Exp(
                        Common(
                          Inconsistent(
                            CompareArrow(
                              sum([
                                Variant("A", [], None),
                                Variant("B", [], Some(arrow(int(), int()))),
                              ]),
                            ),
                          ),
                        ),
                      )
                    ),
                  ),
              )
            )
          ),
        )
      }),

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in-development for PRs that remain in development starter-project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants