diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index 51a6d4d3cd3..d16a9ea0602 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -4801,13 +4801,6 @@ fn body_lower_match_arm_body_optional(body_capture: Node) -> Optional { // resolver instead of vanishing (the loss gunbc#11998 turned into // body_lowering_reason_positional_pattern_binder_unlowered, retired here). Positional arity is // one, as the seed's parse admits; more refuses with a located cause. -fn body_lower_pattern_head_symbol_optional(head: Node) -> Optional { - match body_lower_pattern_leaf_atom_optional(node: head) { - Absent => Absent - Present { value: atom } => node_atom_identity_optional(node: atom) - } -} - fn body_lower_pattern_reject(pattern_capture: Node) -> Outcome { outcome_rejected( d: body_lower_diagnostic(reason: ^body_lowering_reason_pattern_unlowered, n: pattern_capture) @@ -4889,9 +4882,12 @@ fn body_lower_pattern_suffix_lowered(head: Node, suffix: Node, pattern_capture: match body_lower_comma_list_items_optional(list_capture: inner.left) { Absent => body_lower_pattern_reject(pattern_capture: pattern_capture) Present { value: items } => - match body_lower_pattern_head_symbol_optional(head: head) { + match body_lower_pattern_leaf_atom_optional(node: head) { Absent => body_lower_pattern_reject(pattern_capture: pattern_capture) - Present { value: tag } => + Present { value: tag_atom } => + match node_atom_identity_optional(node: tag_atom) { + Absent => body_lower_pattern_reject(pattern_capture: pattern_capture) + Present { value: tag } => bind_outcome( o: body_lower_pattern_field_edges( items: items, @@ -4900,10 +4896,11 @@ fn body_lower_pattern_suffix_lowered(head: Node, suffix: Node, pattern_capture: ), f: fn(edges) { outcome_accepted( - value: construct_node(tag: tag, field_edges: edges, source: pattern_capture) + value: construct_node(tag: tag, tag_source: tag_atom, field_edges: edges, source: pattern_capture) ) } ) + } } } } @@ -7099,7 +7096,14 @@ fn body_lower_record_literal_field_items_optional(captured: Node) -> Optional
  • Optional { +// The tag is read with the atom it was read from, so the construct's tag atom can be lowered from +// that token rather than from the whole capture (v2.std.node_query construct_tag_edge). +type RecordLiteralTag { + tag: Symbol + at: Node +} + +fn body_lower_record_literal_tag_optional(captured: Node) -> Optional { match sugar_sequence_pair_optional(node: captured) { Absent => Absent Present { value: pair } => @@ -7107,7 +7111,7 @@ fn body_lower_record_literal_tag_optional(captured: Node) -> Optional { Present { value: tag } => match dag_lex_token_symbol(sym: tag) { true => optional_absent() - false => optional_present(value: tag) + false => optional_present(value: RecordLiteralTag { tag: tag, at: pair.left }) } Absent => Absent } @@ -7117,7 +7121,7 @@ fn body_lower_record_literal_tag_optional(captured: Node) -> Optional { fn body_lower_try_record_literal(captured: Node) -> Outcome> { match body_lower_record_literal_tag_optional(captured: captured) { Absent => outcome_accepted(value: optional_absent()) - Present { value: tag } => + Present { value: read } => match body_lower_record_literal_field_items_optional(captured: captured) { Absent => outcome_accepted(value: optional_absent()) Present { value: items } => @@ -7131,7 +7135,7 @@ fn body_lower_try_record_literal(captured: Node) -> Outcome> { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: edges, diagnostics: d } => Accepted { - value: optional_present(value: construct_node(tag: tag, field_edges: edges, source: captured)), + value: optional_present(value: construct_node(tag: read.tag, tag_source: read.at, field_edges: edges, source: captured)), diagnostics: d } } diff --git a/src/v2/std/node_query.dag b/src/v2/std/node_query.dag index d000501c94e..c58f91c5e65 100644 --- a/src/v2/std/node_query.dag +++ b/src/v2/std/node_query.dag @@ -41,6 +41,7 @@ import v2.std.node { arrow_body_target_lookup, coproduct_nullary_payload_type_name, named_edge_sort_key, + node_lowered_from, node_with_occurrence_id } import std.constructors { Cardinality, Required, Optional } @@ -260,22 +261,27 @@ fn declared_field_named(payload: Node, name: Symbol) -> Optional // (v2.extdeps.languages.dag dag_rec_construct_body_node, dag_variant_holds_construct_node). A // match-arm PATTERN over a fielded variant is the same construct whose field targets are the // binders, so construction and elimination read one shape. -fn construct_tag_edge(tag: Symbol, source: Node) -> Edge { +// THE TAG ATOM STANDS FOR THE TAG TOKEN, so it is lowered from that token, not from the whole +// construct: `tag_source` is the authored terminal the constructor name was read from, and the +// Conj keeps the construct's own occurrence. Lowering the atom from the enclosing capture gave it +// the shell's occurrence, so the tag's locus was a coarser, different span +// (docs/plans/lowering-occurrence-projection-design.md, rank 3). +fn construct_tag_edge(tag: Symbol, tag_source: Node) -> Edge { Edge { label: Named { name: tag }, - target: node_with_occurrence_id( + target: node_lowered_from( kind: TypeNode { connective: Atom { identity: tag } }, children: [], - occurrence_id: source.occurrence_id + source: tag_source ) } } -fn construct_node(tag: Symbol, field_edges: List, source: Node) -> Node { +fn construct_node(tag: Symbol, tag_source: Node, field_edges: List, source: Node) -> Node { node_with_occurrence_id( kind: TypeNode { connective: Conj }, children: list_append( - left: [construct_tag_edge(tag: tag, source: source)], + left: [construct_tag_edge(tag: tag, tag_source: tag_source)], right: field_edges ), occurrence_id: source.occurrence_id diff --git a/src/v2/test/claim/namespace_xl0/reference_conservation_test.dag b/src/v2/test/claim/namespace_xl0/reference_conservation_test.dag index 6fa66e03405..90f115a3c82 100644 --- a/src/v2/test/claim/namespace_xl0/reference_conservation_test.dag +++ b/src/v2/test/claim/namespace_xl0/reference_conservation_test.dag @@ -303,6 +303,25 @@ fn statement_let_binder_subject() -> ReferenceConservationSubject { conservation_subject(id_tail: "statement_let_binder", source: statement_let_binder_source) } +// ---- constructor tags keep their own occurrence (XL-2 LoweringOccurrenceProjection, rank 3) ----- + +// A record-literal tag (`RcRow { v: n }`) and a constructor pattern (`RcRow { v: x } =>`) each +// lower to a construct whose tag atom v2.std.node_query construct_tag_edge builds. Before the +// repair that atom was lowered from the enclosing capture, so it carried the shell's occurrence and +// both mentions were counted locus-erased. MEASURED on this fixture: 9 conserved / 13 locus-erased +// before, 11 / 11 after -- the two tags and nothing else moved. Every other atom here is a binder, +// a field name or a type position, so the counts are the fixture's own, not a corpus literal. +data construct_tags_source: String = "module v2.test.reference_conservation_construct_tags\n\ntype RcRow {\n v: Int\n}\n\nfn rc_make(n: Int) -> RcRow { RcRow { v: n } }\n\nfn rc_read(r: RcRow) -> Int {\n match r {\n RcRow { v: x } => x\n }\n}\n" + +fn construct_tags_subject() -> ReferenceConservationSubject { + conservation_subject(id_tail: "construct_tags", source: construct_tags_source) +} + +test fn constructor_tags_keep_their_own_occurrence_holds() -> Bool { + let r = conservation_report_of(subject: construct_tags_subject()) + r.authored == 22 && r.conserved == 11 && r.locus_erased == 11 && length(xs: r.dropped) == 0 +} + // ---- anonymous parameter slots are conserved by their minted identity ------------------------- // Each `_` parameter lowers to its own minted binder (v2.std.anonymous_binder), so the label is diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index f0923eeb9e6..5a8cdc43449 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -852,6 +852,7 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.namespace_xl0.reference_conservation.list_literal_argument_subject", "v2.test.claim.namespace_xl0.reference_conservation.where_refinement_predicate_subject", "v2.test.claim.namespace_xl0.reference_conservation.match_later_arm_subject", + "v2.test.claim.namespace_xl0.reference_conservation.construct_tags_subject", "v2.test.claim.namespace_xl0.reference_conservation.statement_let_binder_subject", "v2.test.claim.namespace_xl0.reference_conservation.anonymous_slots_subject", "v2.test.claim.namespace_xl0.reference_conservation_admission.two_same_spelled_labels_subject",