Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions dag/test/claim/emitter_sole_constructor_seal_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,36 @@ fn w_plain_record_keeps_deserialize() -> Bool {
)
}

// THE SEAL REACHES THROUGH NESTED COPRODUCTS. A seal held two coproducts deep (Outer's variant
// carries Inner, whose variant carries Sealed) must drop Deserialize from both containers: deriving
// it on Outer mints a Sealed through serde_json with no mint call, the one-level forge one level
// further out. The walk once read a looked-up coproduct's variants as fields and stopped at the
// coproduct's own name, so Outer kept the derive (v2.std.node EdgeLabel -> StructuralEdgeLabel ->
// CoreEdgeLabel -> BodyTermLoweredSeal refused E0277 in the emitted crate). The plain pair beside it
// is the control: the same shape over an Int must keep Deserialize, or the pattern below matches
// nothing and the red is vacuous.
fn nested_seal_fixture_source() -> String {
"module solector_nested_seal\n\ntype Sealed sole_constructor { admitted: Int }\n\ntype Inner\n = Wrap { s: Sealed }\n | Bare\n\ntype Outer\n = Hold { i: Inner }\n | Vacant\n\ntype PlainInner\n = PlainWrap { n: Int }\n | PlainBare\n\ntype PlainOuter\n = PlainHold { i: PlainInner }\n | PlainVacant\n\nfn mint(v: Int) -> Sealed {\n Sealed { admitted: v }\n}\n"
}

fn w_nested_coproduct_over_a_seal_drops_deserialize() -> Bool {
compile_dag_rust_emit_check(
nested_seal_fixture_source(),
"src/solector_nested_seal.rs",
["pub enum Outer {", "pub enum Inner {"],
["serde::Deserialize)]\n#[serde(tag = \"_variant\")]\npub enum Outer {", "serde::Deserialize)]\n#[serde(tag = \"_variant\")]\npub enum Inner {"]
)
}

fn w_nested_plain_coproduct_keeps_deserialize() -> Bool {
compile_dag_rust_emit_check(
nested_seal_fixture_source(),
"src/solector_nested_seal.rs",
["serde::Deserialize)]\n#[serde(tag = \"_variant\")]\npub enum PlainOuter {", "serde::Deserialize)]\n#[serde(tag = \"_variant\")]\npub enum PlainInner {"],
[]
)
}

test fn sealed_record_field_is_private() -> Bool {
w_sealed_record_field_is_private()
}
Expand Down Expand Up @@ -119,3 +149,11 @@ test fn plain_record_keeps_public_field() -> Bool {
test fn plain_record_keeps_deserialize() -> Bool {
w_plain_record_keeps_deserialize()
}

test fn nested_coproduct_over_a_seal_drops_deserialize() -> Bool {
w_nested_coproduct_over_a_seal_drops_deserialize()
}

test fn nested_plain_coproduct_keeps_deserialize() -> Bool {
w_nested_plain_coproduct_keeps_deserialize()
}
42 changes: 26 additions & 16 deletions src/v1/05_emit_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -8209,7 +8209,7 @@ fn enum_derives(name: String, children: List<Node>, has_fn_fields: Bool, deseria
// same has_fn_fields here rather than deriving it twice.
fn emit_enum_from_children(name: String, type_params: String, generic_param_names: List<String>, children: List<Node>, recursive_types: Set<String>, shared_types: Set<String>, env: TypeEnv, serde_policy: RustEnumWireSerde, emit_info: EmitGraphInfo) -> String {
let has_fn_fields = type_has_fn_fields(name: name, emit_info: emit_info)
let deserialize_forbidden = enum_variant_payloads_forbid_deserialize(name: name, children: children, emit_info: emit_info, source_indices: env.source_indices)
let deserialize_forbidden = members_forbid_deserialize(name: name, members: member_type_nodes(is_product: false, children: children), emit_info: emit_info, source_indices: env.source_indices)
let derives = enum_derives(name: name, children: children, has_fn_fields: has_fn_fields, deserialize_forbidden: deserialize_forbidden, generic_param_names: generic_param_names, emit_info: emit_info, source_indices: env.source_indices)
let effective_serde_policy = if has_fn_fields {
rust_serde_policy(enum_attr: "", rename_prefix: none, rename_suffix: none, rename_style: none)
Expand Down Expand Up @@ -10881,39 +10881,49 @@ fn type_expr_reaches_sealed_carrier(n: Node, emit_info: EmitGraphInfo, source_in
true
} else {
let next_seen = map_insert(seen, name, true)
decl.children |> any(child =>
type_expr_reaches_sealed_carrier(n: child_type_node(ch: child), emit_info: emit_info, source_indices: source_indices, seen: next_seen)
decl_member_type_nodes(decl: decl) |> any(member =>
type_expr_reaches_sealed_carrier(n: member, emit_info: emit_info, source_indices: source_indices, seen: next_seen)
)
}
Absent => false
}
}
}

fn decl_children_forbid_deserialize(name: String, children: List<Node>, emit_info: EmitGraphInfo, source_indices: Map<String, NewlineIndex>) -> Bool {
// THE MEMBER TYPES OF A DECLARATION, read the same way at every depth. A product's members are its
// fields; a coproduct's are its variants' payload fields. The walk once read a LOOKED-UP coproduct's
// children as if they were fields, and child_type_node resolves a variant to the coproduct itself --
// already in the seen-set -- so a seal two coproducts deep was never reached: v2.std.node's
// EdgeLabel -> StructuralEdgeLabel -> CoreEdgeLabel -> BodyTermLoweredSeal kept a derived Deserialize
// on every container and the emitted crate refused E0277. The top-level enum entry read variants
// correctly, so the defect was the two readings disagreeing; one reader closes it.
fn member_type_nodes(is_product: Bool, children: List<Node>) -> List<Node> {
if is_product {
children |> map(field => child_type_node(ch: field))
} else {
children |> flat_map(variant => variant.children |> map(field => child_type_node(ch: field)))
}
}

fn decl_member_type_nodes(decl: Node) -> List<Node> {
member_type_nodes(is_product: is_product_type(n: decl), children: decl.children)
}

fn members_forbid_deserialize(name: String, members: List<Node>, emit_info: EmitGraphInfo, source_indices: Map<String, NewlineIndex>) -> Bool {
let seen = map_insert(empty_map(), qualified_last_segment(name: name), true)
children |> any(child =>
type_expr_reaches_sealed_carrier(n: child_type_node(ch: child), emit_info: emit_info, source_indices: source_indices, seen: seen)
members |> any(member =>
type_expr_reaches_sealed_carrier(n: member, emit_info: emit_info, source_indices: source_indices, seen: seen)
)
}

fn item_forbids_deserialize(item: Node, emit_info: EmitGraphInfo, source_indices: Map<String, NewlineIndex>) -> Bool {
if item_seals_construction(item: item) {
true
} else {
decl_children_forbid_deserialize(name: authored_name_at(source_indices: source_indices, node: item), children: item.children, emit_info: emit_info, source_indices: source_indices)
members_forbid_deserialize(name: authored_name_at(source_indices: source_indices, node: item), members: decl_member_type_nodes(decl: item), emit_info: emit_info, source_indices: source_indices)
}
}

fn enum_variant_payloads_forbid_deserialize(name: String, children: List<Node>, emit_info: EmitGraphInfo, source_indices: Map<String, NewlineIndex>) -> Bool {
let seen = map_insert(empty_map(), qualified_last_segment(name: name), true)
children |> any(variant =>
variant.children |> any(field =>
type_expr_reaches_sealed_carrier(n: child_type_node(ch: field), emit_info: emit_info, source_indices: source_indices, seen: seen)
)
)
}

// A sealed record's fields are private in the emitted crate, so a stored-field read
// must go through the accessor emit_sealed_struct_accessors wrote. THE CHOKE POINT WAS
// ALREADY HERE AND WAS SIMPLY UNROUTED FOR PRODUCTS: the FieldSummary access_style
Expand Down
104 changes: 53 additions & 51 deletions src/v1/stage0/src/v1_compiler_emit_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17473,9 +17473,9 @@ pub fn emit_enum_from_children(
) -> String {
{
let has_fn_fields = type_has_fn_fields(name.clone(), emit_info.clone());
let deserialize_forbidden = enum_variant_payloads_forbid_deserialize(
let deserialize_forbidden = members_forbid_deserialize(
name.clone(),
children.clone(),
member_type_nodes(false, children.clone()),
emit_info.clone(),
env.source_indices.clone(),
);
Expand Down Expand Up @@ -23206,11 +23206,11 @@ pub fn type_expr_reaches_sealed_carrier(
v1_rt::rc_map_insert(seen.clone(), name.clone(), true);
{
let mut __found = false;
for child in decl.children.clone().iter().cloned() {
for member in
decl_member_type_nodes(decl.clone()).iter().cloned()
{
if type_expr_reaches_sealed_carrier(
crate::v1_compiler_infer_types::child_type_node(
child.clone(),
),
member.clone(),
emit_info.clone(),
source_indices.clone(),
next_seen.clone(),
Expand All @@ -23231,9 +23231,50 @@ pub fn type_expr_reaches_sealed_carrier(
})
}

pub fn decl_children_forbid_deserialize(
pub fn member_type_nodes(is_product: bool, children: Rc<Vec<Rc<Node>>>) -> Rc<Vec<Rc<Node>>> {
if is_product.clone() {
Rc::new({
let mut __result = Vec::new();
for field in children.iter().cloned() {
__result.push(crate::v1_compiler_infer_types::child_type_node(
field.clone(),
));
}
__result
})
} else {
Rc::new({
let mut __result = Vec::new();
for variant in children.iter().cloned() {
__result.extend(
(*Rc::new({
let mut __result = Vec::new();
for field in variant.children.clone().iter().cloned() {
__result.push(crate::v1_compiler_infer_types::child_type_node(
field.clone(),
));
}
__result
}))
.iter()
.cloned(),
);
}
__result
})
}
}

pub fn decl_member_type_nodes(decl: Rc<Node>) -> Rc<Vec<Rc<Node>>> {
member_type_nodes(
crate::v1_compiler_infer_types::is_product_type(decl.clone()),
decl.children.clone(),
)
}

pub fn members_forbid_deserialize(
name: String,
children: Rc<Vec<Rc<Node>>>,
members: Rc<Vec<Rc<Node>>>,
emit_info: Rc<EmitGraphInfo>,
source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>,
) -> bool {
Expand All @@ -23245,9 +23286,9 @@ pub fn decl_children_forbid_deserialize(
);
{
let mut __found = false;
for child in children.iter().cloned() {
for member in members.iter().cloned() {
if type_expr_reaches_sealed_carrier(
crate::v1_compiler_infer_types::child_type_node(child.clone()),
member.clone(),
emit_info.clone(),
source_indices.clone(),
seen.clone(),
Expand All @@ -23269,54 +23310,15 @@ pub fn item_forbids_deserialize(
if item_seals_construction(item.clone()) {
true
} else {
decl_children_forbid_deserialize(
members_forbid_deserialize(
crate::v1_std_core::authored_name_at(source_indices.clone(), item.clone()),
item.children.clone(),
decl_member_type_nodes(item.clone()),
emit_info.clone(),
source_indices.clone(),
)
}
}

pub fn enum_variant_payloads_forbid_deserialize(
name: String,
children: Rc<Vec<Rc<Node>>>,
emit_info: Rc<EmitGraphInfo>,
source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>,
) -> bool {
{
let seen = v1_rt::rc_map_insert(
v1_rt::rc_empty_map::<String, bool>(),
crate::v1_std_core::qualified_last_segment(name.clone()),
true,
);
{
let mut __found = false;
for variant in children.iter().cloned() {
if {
let mut __found = false;
for field in variant.children.clone().iter().cloned() {
if type_expr_reaches_sealed_carrier(
crate::v1_compiler_infer_types::child_type_node(field.clone()),
emit_info.clone(),
source_indices.clone(),
seen.clone(),
) {
__found = true;
break;
}
}
__found
} {
__found = true;
break;
}
}
__found
}
}
}

pub fn emit_typed_field_access(
base: Rc<Node>,
field: String,
Expand Down