From 4f09554749ee836a5b5ced14ccd75568e6f172df Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 22 Feb 2022 22:39:28 +0000 Subject: [PATCH] Structs cannot be tested for equality with `==` or `!=`. --- HISTORY.md | 7 +++- TODO.md | 11 ++---- eg/equal-linkedlist.castile | 37 ++++++++++++++++++ eg/equality.castile | 15 -------- eg/length.castile | 2 +- src/castile/backends/c.py | 1 + src/castile/backends/javascript.py | 1 + src/castile/checker.py | 4 +- tests/Castile.md | 61 +++++++----------------------- 9 files changed, 64 insertions(+), 75 deletions(-) create mode 100644 eg/equal-linkedlist.castile delete mode 100644 eg/equality.castile diff --git a/HISTORY.md b/HISTORY.md index 6fb438d..c2fe48b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -14,8 +14,11 @@ Castile 0.5 after the struct. A value of a scope struct can only be `make`d, and the fields of such a value can only be accessed, lexically inside one of the definitions named in the `for`. -* Nested structs can be tested for deep equality. -* Values of union type can be tested for equality. +* Structs cannot be tested for equality with the `==` and `!=` + operators. Instead the programmer should write a function + that compares structs for equality, if desired. +* Values of union type can be tested for equality, but only if + none of the types involved in the union are structs. ### Implementation diff --git a/TODO.md b/TODO.md index 6878dea..e70b643 100644 --- a/TODO.md +++ b/TODO.md @@ -3,13 +3,7 @@ TODO ### Implementation ### -call equal_tagged_value() when you find a union type when -comparing structs deeply. (C backend, probably others) - -Actually the idea of deep equality of structs is suspect. -Especially if the visibility of those structs is limited. -Instead users should write their own `equal` functions -(possibly abstract, possibly only testing equivalence.) +Remove the deep struct equality implementation from backends. Name mangling for compilers (prepend with `_` most likely.) @@ -36,6 +30,9 @@ Other backends (Python? Java? CIL? Scheme?) ### Design ### +Disallow equality checking for union types too (maybe except where +all member types are simple.) + Don't output final value. Command-line arguments passed to `main`. (`sysmain`?) Convenience: diff --git a/eg/equal-linkedlist.castile b/eg/equal-linkedlist.castile new file mode 100644 index 0000000..5475462 --- /dev/null +++ b/eg/equal-linkedlist.castile @@ -0,0 +1,37 @@ +struct list { + value: string; + next: list|void; +} + +fun empty() { + return null as list|void +} + +fun cons(v: string, l: list|void) { + make list(value:v, next:l) as list|void +} + +equal_list : list|void, list|void -> boolean +fun equal_list(a: list|void, b: list|void) { + typecase a is void { + typecase b is void { + return true + } + } + typecase a is list { + typecase b is list { + return a.value == b.value and equal_list(a.next, b.next) + } + } + return false +} + +main = fun() { + l1 = cons("first", cons("second", cons("third", empty()))); + l2 = cons("first", cons("second", cons("third", empty()))); + l3 = cons("first", cons("second", empty())); + + if (equal_list(l1, l2) and not equal_list(l2, l3)) { + print("Yep, story checks out") + } +} diff --git a/eg/equality.castile b/eg/equality.castile deleted file mode 100644 index 40c6418..0000000 --- a/eg/equality.castile +++ /dev/null @@ -1,15 +0,0 @@ -struct name { first: string; last: string } -struct person { age: integer; name: name } - -fun main() { - a = 40 as string|integer - b = 40 as string|integer - if a == b { - print("these union-type values are equal") - } - j = make person(age: 23, name:make name(first:"Bamber", last:"Smith")); - k = make person(age: 23, name:make name(first:"Bamber", last:"Smith")); - if j == k { - print("these deep struct-type values are equal") - } -} diff --git a/eg/length.castile b/eg/length.castile index 1a1a02f..6864ef0 100644 --- a/eg/length.castile +++ b/eg/length.castile @@ -1,7 +1,7 @@ struct list { value: string; next: list|void; -} for (cons, singleton, len) +} for (cons, singleton, length) fun cons(v: string, l: list) { make list(value:v, next:l as list|void) diff --git a/src/castile/backends/c.py b/src/castile/backends/c.py index 3c328ad..16fbac4 100644 --- a/src/castile/backends/c.py +++ b/src/castile/backends/c.py @@ -199,6 +199,7 @@ def compile(self, ast): self.indent -= 1 self.write_indent('}\n\n') + # FIXME the language no longer supports this, it can be jettisoned self.write_indent('int equal_%s(struct %s * a, struct %s * b) {\n' % (ast.value, ast.value, ast.value)) self.indent += 1 diff --git a/src/castile/backends/javascript.py b/src/castile/backends/javascript.py index 20482db..555dca4 100644 --- a/src/castile/backends/javascript.py +++ b/src/castile/backends/javascript.py @@ -72,6 +72,7 @@ def compile(self, ast): elif ast.tag == 'Forward': pass elif ast.tag == 'StructDefn': + # FIXME the language no longer supports this, it can be jettisoned field_defns = ast.children[0].children self.write('function equal_%s(a, b) {\n' % ast.value) for child in field_defns: diff --git a/src/castile/checker.py b/src/castile/checker.py index 5e3ca0f..eb04898 100644 --- a/src/castile/checker.py +++ b/src/castile/checker.py @@ -105,8 +105,8 @@ def type_of(self, ast): type1 = self.type_of(ast.children[0]) type2 = self.type_of(ast.children[1]) self.assert_eq(type1, type2) - if ast.value in ('>', '>=', '<', '<=') and isinstance(type1, Struct): - raise CastileTypeError("structs cannot be compared for order") + if isinstance(type1, Struct): + raise CastileTypeError("structs cannot be compared") ast.type = Boolean() elif ast.tag == 'Not': type1 = self.type_of(ast.children[0]) diff --git a/tests/Castile.md b/tests/Castile.md index d650821..92d9a9d 100644 --- a/tests/Castile.md +++ b/tests/Castile.md @@ -769,8 +769,8 @@ Order of field initialization when making a struct doesn't matter. | } = 23 -Structs can be tested for equality. (Since structs are immutable, it -doesn't matter if this is structural equality or identity.) +Structs cannot be tested for equality with the `==` or `!==` +operators. | struct person { name: string; age: integer } | main = fun() { @@ -778,15 +778,7 @@ doesn't matter if this is structural equality or identity.) | k = make person(name:"Jake", age: 23); | j == k | } - = True - - | struct person { age: integer; name: string } - | main = fun() { - | j = make person(age: 23, name:"Jake"); - | k = make person(age: 23, name:"John"); - | j == k - | } - = False + ? structs cannot be compared | struct person { age: integer; name: string } | main = fun() { @@ -794,7 +786,7 @@ doesn't matter if this is structural equality or identity.) | k = make person(age: 21, name:"Jake"); | j != k | } - = True + ? structs cannot be compared Structs of two different types cannot be tested for equality. @@ -807,44 +799,17 @@ Structs of two different types cannot be tested for equality. | } ? mismatch -Deeply nested structs can be tested for equality. - - | struct name { first: string; last: string } - | struct person { age: integer; name: name } - | main = fun() { - | j = make person(age: 23, name:make name(first:"Bamber", last:"Smith")); - | k = make person(age: 23, name:make name(first:"Bamber", last:"Smith")); - | j == k - | } - = True - - | struct name { first: string; last: string } - | struct person { age: integer; name: name } - | main = fun() { - | j = make person(age: 23, name:make name(first:"Bamber", last:"Smith")); - | k = make person(age: 23, name:make name(first:"Amber", last:"Smith")); - | j != k - | } - = True - -Deeply nested structs can be tested for equality, even when containing values -of union type. +If you really want to compare two structs for equality, you'll +have to write the equality predicate function yourself. - | struct name { first: string; last: string|integer } - | struct person { age: integer; name: name } - | main = fun() { - | j = make person(age: 23, name:make name(first:"Bamber", last:"Smith" as string|integer)); - | k = make person(age: 23, name:make name(first:"Bamber", last:"Smith" as string|integer)); - | j == k + | struct person { name: string; age: integer } + | equ_person = fun(a: person, b: person) { + | a.age == b.age and a.name == b.name | } - = True - - | struct name { first: string; last: string|integer } - | struct person { age: integer; name: name } | main = fun() { - | j = make person(age: 23, name:make name(first:"Bamber", last:"Smith" as string|integer)); - | k = make person(age: 23, name:make name(first:"Bamber", last:75 as string|integer)); - | j != k + | j = make person(age: 23, name:"Jake"); + | k = make person(name:"Jake", age: 23); + | equ_person(j, k) | } = True @@ -856,7 +821,7 @@ Structs cannot be compared for ordering. | k = make person(age: 21, name:"Jake"); | j > k | } - ? structs cannot be compared for order + ? structs cannot be compared Structs can be passed to functions.