Skip to content

Commit 16afe1e

Browse files
cristianocclaude
andauthored
Require mutability backing in private-row signature inclusion
The private/open-object manifest path in includecore compared paired fields by type only, so a signature could declare type t = private {..@set "x": int} over an implementation whose field was not settable - and since access follows the published row, clients could write straight through the abstraction. In the phantom-setter encoding this could not happen structurally: granting required an interface "x#=" member with no implementation partner (rejected by the missing- field check), while forgetting was the interface simply omitting the member (absorbed by the ignored implementation-side misses). Stage D turned the capability into a flag on the field, and nothing had taken over the width mechanism's job. The pairing now requires a settable implementation field wherever the interface field is settable; an implementation's settable field may still be abstracted to a read-only one. Probe-verified equivalent to the released (phantom-encoding) compiler in all directions, including that paired field types remain compared by equality - private rows allow width and capability forgetting, never depth subtyping. The @set inclusion matrix is now pinned per comparison arm, since the flag participates in several independently-changeable relations: object_private_row_grants_set (the new includecore rule), object_manifest_set_mismatch (transparent manifests are equations - eqtype), object_value_signature_set_mismatch (value signatures claim instances - moregeneral), and the legal forgetting direction compiles in object_mutability_pin.res. 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 4d5d3a4 commit 16afe1e

10 files changed

Lines changed: 135 additions & 2 deletions

compiler/ml/includecore.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ let type_manifest env ty1 params1 ty2 params2 priv2 =
137137
&&
138138
let pairs, _miss1, miss2 = Ctype.associate_fields fields1 fields2 in
139139
miss2 = []
140+
(* The signature must not grant write capability the implementation
141+
lacks; an implementation's settable field may be abstracted to a
142+
read-only one. *)
143+
&& List.for_all
144+
(fun ((f1 : Ctype.field_info), (f2 : Ctype.field_info)) ->
145+
match Btype.mutability_repr f2.f_mut with
146+
| Mutable -> Btype.mutability_repr f1.f_mut = Asttypes.Mutable
147+
| Immutable -> true)
148+
pairs
140149
&&
141150
let tl1, tl2 =
142151
List.split

tests/ERROR_VARIANTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ Wrapper symptoms attached to inclusion failures. Source: [includemod.ml:23](../c
363363
| Variant | Status | Fixture | Notes |
364364
|---|---|---|---|
365365
| `Missing_field` || `super_errors_multi/Iface_missing_value` | |
366-
| `Value_descriptions` || `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Iface_value_arity_mismatch`, `super_errors_multi/Smoke_interface_mismatch`, `super_errors_multi/Cross_external_spec_mismatch`, `super_errors_multi/Cross_external_payload_name`, `super_errors_multi/Cross_external_import_attrs`, `module_sig_value_arity_mismatch*.res` | Arity mismatches print a dedicated hint (implementation vs interface argument counts), including through aliases and nested function types. |
366+
| `Value_descriptions` || `super_errors_multi/Iface_value_descriptions`, `super_errors_multi/Iface_value_arity_mismatch`, `super_errors_multi/Smoke_interface_mismatch`, `super_errors_multi/Cross_external_spec_mismatch`, `super_errors_multi/Cross_external_payload_name`, `super_errors_multi/Cross_external_import_attrs`, `module_sig_value_arity_mismatch*.res`, `object_value_signature_set_mismatch.res` | Arity mismatches print a dedicated hint (implementation vs interface argument counts), including through aliases and nested function types. The object fixture pins that a value signature cannot drop `@set` from an object type (moregeneral requires equal field mutability). |
367367
| `Type_declarations` || `super_errors_multi/Iface_type_decl_record`, `super_errors_multi/Iface_type_decl_variant`, `RecordInclusion.res`, `type_decl_function_arity_mismatch.res` | |
368368
| `Extension_constructors` || `super_errors_multi/Iface_extension_constructors` | |
369369
| `Module_types` || `super_errors_multi/Iface_module_types` | |
@@ -387,7 +387,7 @@ Source: [includecore.ml:159](../compiler/ml/includecore.ml).
387387
| `Privacy` || `super_errors_multi/Iface_privacy_mismatch` | |
388388
| `Kind` || `super_errors_multi/Iface_kind_mismatch` | Record-in-impl vs variant-in-interface. |
389389
| `Constraint` || `super_errors_multi/Iface_constraint_mismatch` | Implementation adds a `constraint 'a = …`; interface has none. |
390-
| `Manifest` || `super_errors_multi/Iface_manifest_mismatch`, `type_decl_function_arity_mismatch.res` | Manifest types differ, including function types with different arities. |
390+
| `Manifest` || `super_errors_multi/Iface_manifest_mismatch`, `type_decl_function_arity_mismatch.res`, `object_private_row_grants_set.res`, `object_manifest_set_mismatch.res` | Manifest types differ, including function types with different arities. The object fixtures pin the `@set` inclusion matrix: a private row's signature cannot grant `@set` its implementation lacks (but may forget it — pinned compiling in `object_mutability_pin.res`), while a transparent manifest is an equation and cannot forget it either. |
391391
| `Variance` || `super_errors_multi/Iface_variance_mismatch` | Interface annotates `+'a`; implementation's inferred variance differs. |
392392
| `Field_type` || `super_errors_multi/Iface_type_decl_record` | |
393393
| `Field_mutable` || `super_errors_multi/Iface_field_mutable_mismatch` | |
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_manifest_set_mismatch.res:6:5-8:1
4+
5+
4 │ module M: {
6+
5 │ type t = {"x": int}
7+
6 │ } = {
8+
7 │  type t = {@set "x": int}
9+
8 │ }
10+
9 │
11+
12+
Signature mismatch:
13+
Modules do not match:
14+
{
15+
type t = {@set "x": int}
16+
}
17+
is not included in
18+
{
19+
type t = {"x": int}
20+
}
21+
Type declarations do not match:
22+
type t = {@set "x": int}
23+
is not included in
24+
type t = {"x": int}
25+
/.../fixtures/object_manifest_set_mismatch.res:5:3-21:
26+
Expected declaration
27+
/.../fixtures/object_manifest_set_mismatch.res:7:3-26:
28+
Actual declaration
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_private_row_grants_set.res:7:5-9:1
4+
5+
5 │ module M: {
6+
6 │ type t = private {..@set "x": int}
7+
7 │ } = {
8+
8 │  type t = private {.."x": int}
9+
9 │ }
10+
10 │
11+
12+
Signature mismatch:
13+
Modules do not match:
14+
{
15+
type t = {.."x": int}
16+
}
17+
is not included in
18+
{
19+
type t = {..@set "x": int}
20+
}
21+
Type declarations do not match:
22+
type t = {.."x": int}
23+
is not included in
24+
type t = {..@set "x": int}
25+
/.../fixtures/object_private_row_grants_set.res:6:3-36:
26+
Expected declaration
27+
/.../fixtures/object_private_row_grants_set.res:8:3-31:
28+
Actual declaration
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/object_value_signature_set_mismatch.res:7:5-9:1
4+
5+
5 │ module M: {
6+
6 │ let v: {"x": int}
7+
7 │ } = {
8+
8 │  let v = impl
9+
9 │ }
10+
10 │
11+
12+
Signature mismatch:
13+
Modules do not match:
14+
{
15+
let v: {@set "x": int}
16+
}
17+
is not included in
18+
{
19+
let v: {"x": int}
20+
}
21+
Values do not match:
22+
let v: {@set "x": int}
23+
is not included in
24+
let v: {"x": int}
25+
/.../fixtures/object_value_signature_set_mismatch.res:6:3-19:
26+
Expected declaration
27+
/.../fixtures/object_value_signature_set_mismatch.res:8:7:
28+
Actual declaration
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* A transparent manifest is an equation: unlike a private row
2+
(object_private_row_grants_set.res) or a coercion, it cannot forget a
3+
field's @set - the flags must be equal in both directions. */
4+
module M: {
5+
type t = {"x": int}
6+
} = {
7+
type t = {@set "x": int}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* A signature must not grant write capability its implementation lacks:
2+
the interface's @set field requires a settable implementation field. The
3+
reverse direction (implementation @set abstracted to read-only) is legal
4+
and pinned in tests/tests/src/object_mutability_pin.res. */
5+
module M: {
6+
type t = private {..@set "x": int}
7+
} = {
8+
type t = private {.."x": int}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* A value signature claims an instance of the implementation's type:
2+
moregeneral requires equal field mutability, so an interface cannot
3+
silently drop @set from a value's object type. */
4+
@val external impl: {@set "x": int} = "impl"
5+
module M: {
6+
let v: {"x": int}
7+
} = {
8+
let v = impl
9+
}

tests/tests/src/object_mutability_pin.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ function closed_immutable_covariant(v) {
4949
return v;
5050
}
5151

52+
let PrivateRowForgetsSet = {};
53+
5254
export {
5355
forget_write_covariant,
5456
open_source_covariant,
@@ -62,5 +64,6 @@ export {
6264
set_at_int,
6365
set_at_string,
6466
closed_immutable_covariant,
67+
PrivateRowForgetsSet,
6568
}
6669
/* No side effect */

tests/tests/src/object_mutability_pin.res

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,14 @@ let set_at_string = () => set_x(string_target, "s")
5858

5959
/* Closed immutable-to-immutable coercion is covariant (matrix pin). */
6060
let closed_immutable_covariant = (v: {"x": wide}): {"x": narrow} => (v :> {"x": narrow})
61+
62+
/* Private-row signature inclusion may forget write capability: the
63+
implementation's settable field is abstracted to a read-only one. The
64+
reverse (a signature granting @set over a plain implementation field) is
65+
pinned as an error in
66+
tests/build_tests/super_errors/fixtures/object_private_row_grants_set.res. */
67+
module PrivateRowForgetsSet: {
68+
type t = private {.."x": int}
69+
} = {
70+
type t = private {..@set "x": int}
71+
}

0 commit comments

Comments
 (0)