Skip to content

Commit ac9c310

Browse files
cristianocclaude
andcommitted
Pin current object-field mutability behavior ahead of representation cleanup
Add behavior-pinning tests for the structural-object mutability semantics (currently encoded via phantom "x#=" setter members), ahead of the staged representation cleanup proposed in #8584. tests/tests/src/object_mutability_pin.res pins the compiling cases: closed mutable-to-immutable covariance, open-source and open-target coercions, assignment- and coercion-driven strengthening of open rows, and a generalized getter used at both mutabilities. Two cases are marked EXPECTED TO FLIP with the rationale in place: the unequal-type coercion and the unrelated-type assignment (getter int acquiring setter string), which today produces a value of declared type int that is the string "hello" at runtime - the type-preservation failure the cleanup closes. Seven super_errors fixtures pin the rejecting directions: closed-row writes, both-open invariance, readonly-to-mutable coercions, mutable-to-mutable invariance with unequal types, read-only callers against strengthened rows, and writes after a closed-source-to-open-target coercion. Part of #8584. Signed-Off-By: Cristiano Calcagno <ccrisccris@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PCtQiaDijUqA2fujQXvKUw
1 parent fbe156d commit ac9c310

16 files changed

Lines changed: 287 additions & 0 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_coercion_mutable_unequal.res:8:35-57
4+
5+
6 │ type wide = {"a": int, "b": int}
6+
7 │ type narrow = {"a": int}
7+
8 │ let p = (v: {@set "x": wide}) => (v :> {@set "x": narrow})
8+
9 │
9+
10+
Type {"x": wide, "x#=": wide => unit} is not a subtype of
11+
{"x": narrow, "x#=": narrow => unit}
12+
Type narrow = {"a": int} is not a subtype of wide = {"a": int, "b": int}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_coercion_open_open.res:9:32-51
4+
5+
7 │ type wide = {"a": int, "b": int}
6+
8 │ type narrow = {"a": int}
7+
9 │ let p = (o: {.."x": wide}) => (o :> {.."x": narrow})
8+
10 │
9+
10+
Type {.."x": wide} is not a subtype of {.."x": narrow}
11+
Type wide = {"a": int, "b": int} is not compatible with type
12+
narrow = {"a": int}
13+
14+
The second object is expected to have a field "b" of type int, but it does not.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_coercion_promote_readonly_caller.res:11:11-18
4+
5+
9 │ let f = (o: {.."x": wide}) => (o :> {@set "x": wide})
6+
10 │ @val external readonly: {"x": wide} = "readonly"
7+
11 │ let _ = f(readonly)
8+
12 │
9+
10+
This has type: {"x": wide}
11+
But this function argument is expecting: {.."x": wide, "x#=": wide => unit}
12+
13+
The first object is expected to have a field "x#=" of type wide => unit, but it does not.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_coercion_readonly_to_mutable.res:6:20-39
4+
5+
4 │ See docs/object_representation_cleanup.md. */
6+
5 │ type t = {"x": int}
7+
6 │ let p = (v: t) => (v :> {@set "x": int})
8+
7 │
9+
10+
Type t = {"x": int} is not a subtype of {"x": int, "x#=": int => unit}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_open_write_readonly_caller.res:8:11-18
4+
5+
6 │ let f = (o: {.."x": int}) => o["x"] = 1
6+
7 │ @val external readonly: {"x": int} = "readonly"
7+
8 │ let _ = f(readonly)
8+
9 │
9+
10+
This has type: {"x": int}
11+
But this function argument is expecting: {.."x": int, "x#=": int => unit}
12+
13+
The first object is expected to have a field "x#=" of type int => unit, but it does not.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_write_after_open_target_coercion.res:12:3
4+
5+
10 │ let p = (v: {"x": wide}) => {
6+
11 │ let r = (v :> {.."x": narrow})
7+
12 │ r["x"] = {"a": 1}
8+
13 │ }
9+
14 │
10+
11+
This expression has type {"x": narrow}
12+
It has no field x#=
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_write_closed_row.res:6:28
4+
5+
4 │ See docs/object_representation_cleanup.md; compiling counterparts in
6+
5 │ tests/tests/src/object_mutability_pin.res. */
7+
6 │ let g = (o: {"x": int}) => o["x"] = 1
8+
7 │
9+
10+
This expression has type {"x": int}
11+
It has no field x#=
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* Pin (object mutability cleanup): mutable-to-mutable coercion is invariant
2+
in the field type — with unequal types it is rejected (today the setter
3+
member demands contravariance while the getter demands covariance). Must
4+
stay an error under the new model (Mutable A <: Mutable B iff A = B).
5+
See docs/object_representation_cleanup.md. */
6+
type wide = {"a": int, "b": int}
7+
type narrow = {"a": int}
8+
let p = (v: {@set "x": wide}) => (v :> {@set "x": narrow})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* Pin (object mutability cleanup): when BOTH rows are open, object fields
2+
are invariant — this covariant coercion is rejected. Principled, not an
3+
artifact: an open result is a promotable result, and a covariantly
4+
weakened field must never remain promotable (a later write at the narrow
5+
type would reach readers at the wide type). Must stay an error under the
6+
new model. See docs/object_representation_cleanup.md. */
7+
type wide = {"a": int, "b": int}
8+
type narrow = {"a": int}
9+
let p = (o: {.."x": wide}) => (o :> {.."x": narrow})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* Pin (object mutability cleanup): COERCION-driven strengthening (as
2+
opposed to the assignment-driven case in
3+
object_open_write_readonly_caller.res): coercing an open-row parameter to
4+
a same-type mutable target constrains the row, so a read-only caller is
5+
rejected. Both halves must survive the new model (the coercion promotes
6+
the open source's field; the demand becomes a Mutable field).
7+
See docs/object_representation_cleanup.md. */
8+
type wide = {"a": int, "b": int}
9+
let f = (o: {.."x": wide}) => (o :> {@set "x": wide})
10+
@val external readonly: {"x": wide} = "readonly"
11+
let _ = f(readonly)

0 commit comments

Comments
 (0)