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
32 changes: 18 additions & 14 deletions src/v2/compiler/body_lowering_fold.dag
Original file line number Diff line number Diff line change
Expand Up @@ -4801,13 +4801,6 @@ fn body_lower_match_arm_body_optional(body_capture: Node) -> Optional<Node> {
// 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<Symbol> {
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<T>(pattern_capture: Node) -> Outcome<T> {
outcome_rejected(
d: body_lower_diagnostic(reason: ^body_lowering_reason_pattern_unlowered, n: pattern_capture)
Expand Down Expand Up @@ -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,
Expand All @@ -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)
)
}
)
}
}
}
}
Expand Down Expand Up @@ -7099,15 +7096,22 @@ fn body_lower_record_literal_field_items_optional(captured: Node) -> Optional<Li
}
}

fn body_lower_record_literal_tag_optional(captured: Node) -> Optional<Symbol> {
// 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<RecordLiteralTag> {
match sugar_sequence_pair_optional(node: captured) {
Absent => Absent
Present { value: pair } =>
match dag_binding_branded_atom_identity(node: pair.left) {
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
}
Expand All @@ -7117,7 +7121,7 @@ fn body_lower_record_literal_tag_optional(captured: Node) -> Optional<Symbol> {
fn body_lower_try_record_literal(captured: Node) -> Outcome<Optional<Node>> {
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 } =>
Expand All @@ -7131,7 +7135,7 @@ fn body_lower_try_record_literal(captured: Node) -> Outcome<Optional<Node>> {
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
}
}
Expand Down
16 changes: 11 additions & 5 deletions src/v2/std/node_query.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -260,22 +261,27 @@ fn declared_field_named(payload: Node, name: Symbol) -> Optional<DeclaredField>
// (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<Edge>, source: Node) -> Node {
fn construct_node(tag: Symbol, tag_source: Node, field_edges: List<Edge>, 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
Expand Down
19 changes: 19 additions & 0 deletions src/v2/test/claim/namespace_xl0/reference_conservation_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/v2/workflow/floor_pure_producer_share.dag
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ data floor_cross_claim_pure_producers_warm: List<String> = [
"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",
Expand Down