Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
55cc265
MQ-1 PR-2 WIP: caret symbol lowers to a symbol literal
Sep 27, 2026
f2ad6a8
MQ-1 PR-2: RFMs, conservation control, symbol-literal frontier control
Sep 27, 2026
a926369
symbol literal payload read without a nested Edge pattern (emitted Ru…
Sep 27, 2026
0f8ca29
caret lowering claims: list_snoc_item from v2.std.algebra; declare th…
Sep 27, 2026
2db345f
parse probe: caret-site claims read one warm-shared parse (caret_tree…
Sep 27, 2026
3237099
caret lowering witness: plain recursion instead of fn-lambda call arg…
Sep 27, 2026
debbced
caret lowering witness: build lists with list literals/concat (seed t…
Sep 27, 2026
032ac22
Merge origin/main into session/vivid-ant-536
Sep 28, 2026
4aef862
v2 resolve: a module's own `type Int` / `type Bool` shadows the kerne…
Sep 28, 2026
62b0b0b
v2 resolve: an imported user Int refuses instead of binding the kerne…
Sep 28, 2026
fa8c596
v2 resolve: an ambiguous kernel spelling refuses unless every candida…
Sep 28, 2026
404f259
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 28, 2026
b4d46ee
wip: infer symbol-literal arm (DagCanonicalSymbolLiteral typed as v2.…
Sep 28, 2026
32194a7
test.claim.parse_test_fn_decl_return_clause: lowering carries the aut…
Sep 28, 2026
5eae95f
Merge branch 'session/neat-ibex-696' into neat-ibex-696/symbol-arm
Sep 28, 2026
9dab283
dag_canonical_literal_from_node: match the symbol-literal optional once
Sep 28, 2026
3fb5d6a
Dissolve dag_node_is_symbol_literal_atom: callers match dag_symbol_li…
Sep 28, 2026
9a5666a
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 28, 2026
91409e2
identity_captured_navigation: read a caret literal's name from the le…
Sep 28, 2026
5dd210a
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 28, 2026
ebbdff8
Delete the octet-to-scalar index and its lens-slice claims with the l…
Sep 28, 2026
ccf61dd
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 28, 2026
db6b83b
caret_symbol_has_no_lowered_form: the receipt states the literal's ty…
Sep 28, 2026
a4ab128
v2 infer: the facts map is a Node-keyed Map built once, not a list sc…
Sep 28, 2026
9a8b370
Merge remote-tracking branch 'origin/main' into session/vivid-ant-536
Sep 28, 2026
b55b8a7
v2 resolve: the single-tree namespace binds the module's own declarat…
Sep 28, 2026
ba77453
Merge branch 'session/neat-ibex-696' into neat-ibex-696/symbol-arm
Sep 28, 2026
e519bda
v2 resolve: select a grafted module body by the graft's mark, never b…
Sep 28, 2026
6384e52
Merge branch 'session/neat-ibex-696' into neat-ibex-696/symbol-arm
Sep 28, 2026
658ca0f
v2 resolve: move build_program_namespace's rationale above the declar…
Sep 28, 2026
2c78a91
Merge branch 'session/neat-ibex-696' into neat-ibex-696/symbol-arm
Sep 28, 2026
0f2cc9d
Merge branch 'pr12549' into session/eager-newt-412
Sep 28, 2026
b59537a
v2 infer: a key carrying two different facts refuses (infer_facts_key…
Sep 28, 2026
cab2674
authored-occurrence census: the fact-subject key's uniqueness premise…
Sep 29, 2026
7dfd5b0
census: annotation adjacency; hoist matches out of argument position
Sep 29, 2026
41c8480
fkc: Rejected carries NonEmptyDiagnostics; record infer_parameter_sco…
Sep 29, 2026
7c443f2
Revert "census: annotation adjacency; hoist matches out of argument p…
Sep 29, 2026
f8a96ac
Revert "authored-occurrence census: the fact-subject key's uniqueness…
Sep 29, 2026
f1b9ee0
Merge #12557 (node-keyed facts map); the conflict refusal moves into …
Sep 29, 2026
68ef51a
Merge main into session/eager-newt-412: keep the renamed caret_symbol…
Sep 29, 2026
7ea9f2e
Merge origin/main (incl. #12433) into session/vivid-ant-536
Sep 29, 2026
87a29e4
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 29, 2026
1a0f163
Merge branch 'pr12549b' into session/eager-newt-412
Sep 29, 2026
849da15
RFM reference_conservation_population_omits_class_stamped_terminals: …
Sep 29, 2026
92389ff
v2 infer: a literal payload's facts derive for its own family
Sep 29, 2026
58d33f7
Merge branch 'pr12549c' into session/eager-newt-412
Sep 29, 2026
8831f13
body_let_annotation: share the payload verdicts, not the inferred trees
Sep 29, 2026
c01d18a
Merge branch 'pr12549d' into session/eager-newt-412
Sep 29, 2026
cfa8e3a
Merge main (with #12557 landed): keep the conflict-refusing inferred_…
Sep 29, 2026
4475ef4
Merge origin/main into session/vivid-ant-536 (resolve: kernel-type re…
Sep 29, 2026
6d53179
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 29, 2026
b8298b6
Merge branch 'pr12549e' into session/eager-newt-412
Sep 29, 2026
fed694a
04_infer: the payload comment cites infer_literal_edge_payload, not t…
Sep 29, 2026
f8213c2
Merge remote-tracking branch 'origin/main' into session/vivid-ant-536
Sep 29, 2026
78bf5d2
Merge origin/main; bla_symbol_literal_as_int returns Outcome<Resolved…
Sep 29, 2026
81c1522
Merge origin/main into session/vivid-ant-536 (reference_conservation:…
Sep 29, 2026
da03799
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 29, 2026
973c840
Merge main into session/eager-newt-412: keep #12549's caret-payload s…
Sep 29, 2026
c1e31b4
Merge branch 'pr12549f' into session/eager-newt-412
Sep 29, 2026
2eb1dd1
roster_gate imports Finding (bare channel is off in a file that decla…
Sep 29, 2026
d0f8556
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 29, 2026
b45c8bd
Merge branch 'pr12549g' into session/eager-newt-412
Sep 29, 2026
6348125
Merge origin/main; carry #12615's let-in caret position into caret_sy…
Sep 29, 2026
92602d5
Merge main into session/eager-newt-412: keep #12420's retirement of c…
Sep 29, 2026
ab0ccff
Merge branch 'int-12420c' into session/eager-newt-412
Sep 29, 2026
368b18c
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 29, 2026
796477d
merge #12549 368b18c7ef47
Sep 29, 2026
37ad055
Merge origin/main (keep #12607's return row and #12617's handoff rece…
Sep 29, 2026
d9fd2c9
Merge remote-tracking branch 'origin/session/vivid-ant-536' into neat…
Sep 29, 2026
b986425
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 29, 2026
c81bbc9
Merge #12420 37ad055e3494 into session/eager-newt-412: keep #12420's …
Sep 29, 2026
7a64c8f
Merge remote-tracking branch 'origin/main' into session/eager-newt-412
Sep 29, 2026
b4ccd30
merge #12549 b9864258ae96
Sep 29, 2026
a0a65fd
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 30, 2026
18ffbda
Merge main into session/eager-newt-412 (#12420 landed as a squash; th…
Sep 30, 2026
ceed9f6
merge #12549 a0a65fd57a5a
Sep 30, 2026
b27a773
fkc: controls for the key's structural identity: one minted node reac…
Sep 30, 2026
e056dde
Merge main into session/eager-newt-412: #12549's literal-payload step…
Sep 30, 2026
24a6aaa
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 30, 2026
6e9850b
04_infer: bind the literal payload family once in the gather
Sep 30, 2026
6ff1aae
merge #12549 6e9850bfbbec (its payload-family binding wins in its own…
Sep 30, 2026
36b8c89
Restore #12582's key-conflict refusal in 04_infer (the previous merge…
Sep 30, 2026
7746b2d
Merge main into session/eager-newt-412 (#12714 landed: import list ke…
Sep 30, 2026
2272172
Merge remote-tracking branch 'origin/main' into neat-ibex-696/symbol-arm
Sep 30, 2026
2642525
merge #12549 22721720aee0 (its import order)
Sep 30, 2026
0522284
Merge remote-tracking branch 'origin/main' into session/eager-newt-412
Sep 30, 2026
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
33 changes: 25 additions & 8 deletions src/v2/compiler/04_infer.dag
Original file line number Diff line number Diff line change
Expand Up @@ -937,14 +937,28 @@ fn lookup_inferred_facts_in_entries(
)
}

// THE FACTS MAP IS A NODE-KEYED MAP, BUILT ONCE PER RUN (DESIGN section 6, bare minimum cost): a
// lookup against the admitted entries list was a scan, so every lookup was linear in the tree and
// a consumer walking the tree paid quadratic. Entry order still decides a repeated node: the FIRST
// admitted entry answers, exactly as the scan it replaces did, so a later entry never displaces it.
fn inferred_facts_map_enter(facts: Map<Node, InferredFacts>, entry: InferredFactsEntry) -> Map<Node, InferredFacts> {
// ONE KEY, ONE FACT. The facts map is a node-keyed Map built once per run (DESIGN section 6, bare
// minimum cost: a scan per entry made admission quadratic), and it is admitted here and nowhere else,
// so this is where a key carrying two DIFFERENT facts refuses. The first-entry-wins rule it replaces
// dropped a second, differing fact silently, and every consumer read whichever gather produced first.
// Equal facts under one key are one fact and merge.
fn infer_facts_key_conflict_diagnostic(key: Node) -> Diagnostic {
Diagnostic {
reason: ^infer_facts_key_conflict,
at: node_locus(node: key),
correction: Unavailable { reason: ExternalContractUnknown }
}
}

fn inferred_facts_map_enter(facts: Map<Node, InferredFacts>, entry: InferredFactsEntry) -> Outcome<Map<Node, InferredFacts>> {
match map_lookup(m: facts, key: entry.node) {
Present { value: _ } => facts
Absent => map_insert(m: facts, key: entry.node, value: entry.facts)
Absent => Accepted { value: map_insert(m: facts, key: entry.node, value: entry.facts), diagnostics: None }
Present { value: held } =>
if held == entry.facts {
Accepted { value: facts, diagnostics: None }
} else {
Rejected { diagnostics: diagnostics_singleton(d: infer_facts_key_conflict_diagnostic(key: entry.node)) }
}
}
}

Expand All @@ -954,7 +968,10 @@ fn facts_map_from_entries(entries: List<InferredFactsEntry>) -> Outcome<PartialF
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: admitted, diagnostics: d } =>
if inferred_facts_cover_node(facts: entry.facts, node: entry.node) {
Accepted { value: inferred_facts_map_enter(facts: admitted, entry: entry), diagnostics: d }
match inferred_facts_map_enter(facts: admitted, entry: entry) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: entered, diagnostics: _ } => Accepted { value: entered, diagnostics: d }
}
} else {
Rejected {
diagnostics: diagnostics_singleton(
Expand Down
122 changes: 122 additions & 0 deletions src/v2/test/infer_facts/facts_key_conflict_test.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
module v2.test.infer_facts.facts_key_conflict

import std.occurrence_identity { OccurrenceId, OccurrenceMinted, OccurrenceSynthetic }
import std.algebra { Cons, Empty }
import v2.compiler.infer {
facts_map_from_entries,
infer_canonical_grounding_incoherent_diagnostic,
infer_facts_lookup_miss_diagnostic
}
import v2.compiler.inferred_tree { GroundingNotDerived, InferredFacts, InferredFactsEntry }
import v2.std.diagnostic { Accepted, Rejected, Some, diagnostics_has_reason }
import v2.std.node { Atom, Node, Symbol, TypeNode }
import v2.std.optional { Absent, Present }
import v2.std.witness { Violates }

// The table's admission is the interface under test, so its entries are SUPPLIED: two facts that
// differ only in their descent witness, under one key. The route through gather is the integration
// program's qualification, not this module's subject.

fn fkc_key() -> Node {
Node {
kind: TypeNode { connective: Atom { identity: ^fkc_key } },
children: [],
occurrence_id: OccurrenceSynthetic
}
}

fn fkc_facts_a() -> InferredFacts {
InferredFacts {
grounding: GroundingNotDerived { node: fkc_key() },
descent: Violates { diagnostic: infer_facts_lookup_miss_diagnostic(key: fkc_key()) }
}
}

fn fkc_facts_b() -> InferredFacts {
InferredFacts {
grounding: GroundingNotDerived { node: fkc_key() },
descent: Violates { diagnostic: infer_canonical_grounding_incoherent_diagnostic(node: fkc_key()) }
}
}

fn fkc_entry(facts: InferredFacts) -> InferredFactsEntry {
InferredFactsEntry { node: fkc_key(), facts: facts }
}

// Differing facts under one key refuse with the conflict reason -- asserted by reason, so a refusal
// for any other cause does not green this.
test fn fkc_differing_facts_under_one_key_refuse() -> Bool {
match facts_map_from_entries(entries: [fkc_entry(facts: fkc_facts_a()), fkc_entry(facts: fkc_facts_b())]) {
Rejected { diagnostics: d } => diagnostics_has_reason(d: Some { diagnostics: d }, reason: ^infer_facts_key_conflict)
Accepted { value: _, diagnostics: _ } => false
}
}

// Equal facts under one key are one fact: admitted, and the lookup answers that fact.
test fn fkc_equal_facts_under_one_key_merge() -> Bool {
match facts_map_from_entries(entries: [fkc_entry(facts: fkc_facts_a()), fkc_entry(facts: fkc_facts_a())]) {
Rejected { diagnostics: _ } => false
Accepted { value: table, diagnostics: _ } =>
match table.lookup(fkc_key()) {
Present { value: f } => f == fkc_facts_a()
Absent => false
}
}
}

// Positive control for the discriminator: the two supplied facts really differ.
test fn fkc_supplied_facts_differ() -> Bool {
fkc_facts_a() != fkc_facts_b()
}

// THE KEY IS THE WHOLE STRUCTURAL NODE, OCCURRENCE INCLUDED. A node reached twice -- same minted
// occurrence, same structure -- is ONE subject: its equal facts are admitted as one entry, never
// refused. Two structurally DIFFERENT nodes that share a minted occurrence are two keys here; whether
// such a pair may exist at all is the occurrence admission's question (v2.compiler.inferred_tree
// authored_occurrence_collisions), not this table's, so the table must not refuse it as a conflict.
fn fkc_minted_atom(name: Symbol) -> Node {
Node {
kind: TypeNode { connective: Atom { identity: name } },
children: [],
occurrence_id: OccurrenceMinted { id: OccurrenceId { value: 7 } }
}
}

fn fkc_minted_entry(node: Node) -> InferredFactsEntry {
InferredFactsEntry {
node: node,
facts: InferredFacts {
grounding: GroundingNotDerived { node: node },
descent: Violates { diagnostic: infer_facts_lookup_miss_diagnostic(key: node) }
}
}
}

test fn fkc_same_minted_node_reached_twice_is_one_subject() -> Bool {
let n = fkc_minted_atom(name: ^fkc_same)
match facts_map_from_entries(entries: [fkc_minted_entry(node: n), fkc_minted_entry(node: n)]) {
Rejected { diagnostics: _ } => false
Accepted { value: table, diagnostics: _ } =>
match table.lookup(n) {
Present { value: _ } => true
Absent => false
}
}
}

test fn fkc_different_nodes_sharing_a_minted_id_are_two_keys() -> Bool {
let a = fkc_minted_atom(name: ^fkc_left)
let b = fkc_minted_atom(name: ^fkc_right)
match facts_map_from_entries(entries: [fkc_minted_entry(node: a), fkc_minted_entry(node: b)]) {
Rejected { diagnostics: _ } => false
Accepted { value: table, diagnostics: _ } =>
match table.lookup(a) {
Present { value: fa } =>
match table.lookup(b) {
Present { value: fb } => fa != fb
Absent => false
}
Absent => false
}
}
}