From d31345b37b07377ea2e5955fdd62db7e4df69fe5 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 17:23:56 +0000 Subject: [PATCH 1/7] WIP: Disposition owner parameter --- .../decl_facts_reflection/specimens.dag | 39 ++ src/v1/stage0/src/coproduct_reflection.rs | 17 +- .../decl_facts_skeleton_reflection_test.rs | 387 ++++++++++++++++++ src/v1/tests/src/lib.rs | 2 + 4 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 dag/test/fixture/decl_facts_reflection/specimens.dag create mode 100644 src/v1/tests/src/decl_facts_skeleton_reflection_test.rs diff --git a/dag/test/fixture/decl_facts_reflection/specimens.dag b/dag/test/fixture/decl_facts_reflection/specimens.dag new file mode 100644 index 00000000000..d5f863d86d4 --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/specimens.dag @@ -0,0 +1,39 @@ +module test.fixture.decl_facts_reflection.specimens + +import std.types { String, Int } +import std.disposition { Disposition, Scaffold, Terminal, SingleAuthority } +import std.decl_ref { DeclarationRef, WholeDeclaration, NamedField } + +data planted_scaffold_specimen: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "planted_scaffold_specimen", + field: WholeDeclaration + } +} + +data planted_terminal_specimen: Disposition = Terminal { + reason: "fixture terminal arm" +} + +data planted_nullary_disposition_specimen: Disposition = SingleAuthority + +data planted_named_field_ref_specimen: DeclarationRef = DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "planted_scaffold_specimen", + field: NamedField { field_name: "dissolves_to" } +} + +data planted_plain_int_specimen: Int = 42 + +data planted_plain_string_specimen: String = "hello" + +fn uses_param_only(x: Int) -> Int { + x +} + +fn uses_local(y: Int) -> Int { + let z = y + z +} diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 8f98db771d8..5be82c8e2de 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -664,12 +664,27 @@ fn marshal_generic( let name = node_authored_name(node, si); let mut edges: Vec = Vec::with_capacity(node.children.len() + 1); let mut refs: std::collections::BTreeSet = std::collections::BTreeSet::new(); - if node_references_param(node, &name, param_names) { + if matches!(node.expr_data.as_ref(), ExprData::ExprRecordLit { .. }) && !name.is_empty() { + // Outer coproduct/record variant constructor (e.g. Scaffold, Terminal, DeclarationRef). + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } else if node_references_param(node, &name, param_names) { edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); } else if matches!(node.expr_data.as_ref(), ExprData::ExprCall { .. }) && !name.is_empty() { // G2 live-read call reachability: callee atoms make cross-fn carrier chains // visible in the fn-arrow skeleton (docs/plans/live-read-witness-classification-design.md P1). edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } else if !name.is_empty() { + if let ExprData::ExprVar { binding_kind } = node.expr_data.as_ref() { + if matches!( + binding_kind, + Some(bk) if matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }) + ) && !node_references_param(node, &name, param_names) + { + // Nullary variant VALUE references only (e.g. dissolves_to: SingleAuthority). + // Deliberately excludes binding_kind None (unresolved) and FunctionValueBinding. + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } + } } if let Some(literal_edge) = marshal_string_literal_atom(ctx, node) { edges.push(literal_edge); diff --git a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs new file mode 100644 index 00000000000..425cfb7d48b --- /dev/null +++ b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs @@ -0,0 +1,387 @@ +//! Generic `decl_facts` data-init skeleton reflection prerequisite (step 1 / #7759 split). +//! +//! Narrow marshal arms: outer record-literal variant identity + nullary variant VALUE +//! references (`VariantValueBinding` only). Measured fixture-pool census + negative +//! controls for locals, parameters, function values, and duplicate atoms. + +use std::collections::BTreeSet; +use std::rc::Rc; + +use im::HashMap; +use v1_compiler::coproduct_reflection::{eval_decl_facts, eval_fn_arrow_decl_facts_live}; +use v1_compiler::v1_compiler_infer_emit_info::empty_emit_graph_info; +use v1_compiler::v1_compiler_infer_items::ResolvedGraph; +use v1_compiler::v1_interpreter::{ExecutionMode, InterpContext, Value}; + +use crate::helpers::workspace_root; + +const FIXTURE_POOL: &str = "dag/test/fixture/decl_facts_reflection"; + +const QN_SCAFFOLD: &str = "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen"; +const QN_TERMINAL: &str = "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen"; +const QN_NULLARY: &str = + "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen"; +const QN_NAMED_FIELD_REF: &str = + "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen"; +const QN_PLAIN_INT: &str = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen"; +const QN_FN_PARAM: &str = "test.fixture.decl_facts_reflection.specimens.uses_param_only"; +const QN_FN_LOCAL: &str = "test.fixture.decl_facts_reflection.specimens.uses_local"; + +fn wet_ctx() -> InterpContext { + let graph = ResolvedGraph { + modules: Rc::new(im::vector![]), + item_registry: Rc::new(HashMap::new()), + diagnostics: Rc::new(im::vector![]), + emit_graph_info: empty_emit_graph_info(), + }; + InterpContext::new(&graph, Rc::new(HashMap::new()), ExecutionMode::Wet) +} + +fn fixture_roots() -> Vec { + vec![ + workspace_root() + .join(FIXTURE_POOL) + .to_string_lossy() + .into_owned(), + ] +} + +fn collect_atom_identities(val: &Value, ctx: &InterpContext, out: &mut BTreeSet) { + match val { + Value::Variant { + variant_name, + fields, + .. + } => { + if ctx.sym_eq(*variant_name, "Atom") + && let Some((_, Value::Str(identity))) = fields + .iter() + .find(|(k, _)| ctx.sym_eq(*k, "identity")) + { + out.insert(identity.to_string()); + } + for (_, v) in fields.iter() { + collect_atom_identities(v, ctx, out); + } + } + Value::Record { fields, .. } => { + for (_, v) in fields.iter() { + collect_atom_identities(v, ctx, out); + } + } + Value::List(items) => { + for v in items.iter() { + collect_atom_identities(v, ctx, out); + } + } + _ => {} + } +} + +fn count_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> usize { + let mut count = 0usize; + fn walk(val: &Value, ctx: &InterpContext, identity: &str, count: &mut usize) { + match val { + Value::Variant { + variant_name, + fields, + .. + } => { + if ctx.sym_eq(*variant_name, "Atom") + && fields.iter().any(|(k, v)| { + ctx.sym_eq(*k, "identity") + && matches!(v, Value::Str(s) if s.as_str() == identity) + }) + { + *count += 1; + } + for (_, v) in fields.iter() { + walk(v, ctx, identity, count); + } + } + Value::Record { fields, .. } => { + for (_, v) in fields.iter() { + walk(v, ctx, identity, count); + } + } + Value::List(items) => { + for v in items.iter() { + walk(v, ctx, identity, count); + } + } + _ => {} + } + } + walk(val, ctx, identity, &mut count); + count +} + +fn value_contains_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> bool { + count_atom_identity(val, ctx, identity) > 0 +} + +fn decl_fact_node_skeleton( + ctx: &InterpContext, + rows: &Value, + qualified_name: &str, +) -> Option { + let items = match rows { + Value::List(items) => items, + other => panic!("expected decl_facts List, got {other:?}"), + }; + for row in items.iter() { + let fields = match row { + Value::Record { fields, .. } => fields.as_ref(), + other => panic!("expected DeclFact Record, got {other:?}"), + }; + let qn = match ctx.field(fields, "qualified_name") { + Some(Value::Str(s)) => s.as_str(), + other => panic!("expected qualified_name Str, got {other:?}"), + }; + if qn != qualified_name { + continue; + } + return ctx.field(fields, "node").cloned(); + } + None +} + +fn fn_arrow_body_skeleton( + ctx: &InterpContext, + rows: &Value, + qualified_name: &str, +) -> Option { + let items = match rows { + Value::List(items) => items, + other => panic!("expected fn_arrow_decl_facts List, got {other:?}"), + }; + for row in items.iter() { + let fields = match row { + Value::Record { fields, .. } => fields.as_ref(), + other => panic!("expected FnArrowDeclFact Record, got {other:?}"), + }; + let qn = match ctx.field(fields, "qualified_name") { + Some(Value::Str(s)) => s.as_str(), + other => panic!("expected qualified_name Str, got {other:?}"), + }; + if qn != qualified_name { + continue; + } + return ctx.field(fields, "body").cloned(); + } + None +} + +fn fixture_decl_facts(ctx: &InterpContext) -> Value { + eval_decl_facts(ctx, &fixture_roots()).expect("eval_decl_facts") +} + +fn fixture_fn_arrow_facts(ctx: &InterpContext) -> Value { + eval_fn_arrow_decl_facts_live(ctx, &[]).expect("eval_fn_arrow_decl_facts_live") +} + +fn census_fixture_pool_data_init_atoms(ctx: &InterpContext) -> (usize, BTreeSet) { + let rows = fixture_decl_facts(ctx); + let items = match &rows { + Value::List(items) => items, + other => panic!("expected List, got {other:?}"), + }; + let mut all = BTreeSet::new(); + for row in items.iter() { + let fields = match row { + Value::Record { fields, .. } => fields.as_ref(), + _ => continue, + }; + let kind = ctx.field(fields, "kind"); + let is_data = matches!( + kind, + Some(Value::Variant { + variant_name, + fields: kf, + .. + }) if ctx.sym_eq(*variant_name, "DataItem") && kf.is_empty() + ); + if !is_data { + continue; + } + if let Some(node) = ctx.field(fields, "node") { + collect_atom_identities(node, ctx, &mut all); + } + } + let count = all.len(); + (count, all) +} + +#[test] +fn outer_variant_identity_scaffold_specimen() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_SCAFFOLD) + .expect("missing scaffold specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "Scaffold"), + "Scaffold outer variant constructor must appear in skeleton" + ); + assert_eq!( + count_atom_identity(&skeleton, &ctx, "Scaffold"), + 1, + "Scaffold constructor atom must appear exactly once (no duplicate emission)" + ); +} + +#[test] +fn outer_variant_identity_terminal_specimen() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_TERMINAL) + .expect("missing terminal specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "Terminal"), + "Terminal outer variant constructor must appear in skeleton" + ); +} + +#[test] +fn nullary_variant_value_atom_in_scaffold_specimen() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_SCAFFOLD) + .expect("missing scaffold specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "nullary ConstructionMechanism variant value must surface as atom" + ); +} + +#[test] +fn nullary_variant_value_atom_in_nullary_disposition_specimen() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_NULLARY) + .expect("missing nullary disposition specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "top-level nullary Disposition arm must surface variant identity" + ); +} + +#[test] +fn declaration_ref_fields_present_in_scaffold_bind() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_SCAFFOLD) + .expect("missing scaffold specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "DeclarationRef"), + "DeclarationRef constructor must appear in bind conj" + ); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "test.fixture.decl_facts_reflection.specimens"), + "module_path literal must appear in bind conj" + ); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "planted_scaffold_specimen"), + "decl_name literal must appear in bind conj" + ); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "WholeDeclaration"), + "WholeDeclaration field arm must appear in bind conj" + ); +} + +#[test] +fn named_field_declaration_ref_fields_present() { + let ctx = wet_ctx(); + let skeleton = + decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_NAMED_FIELD_REF) + .expect("missing named-field ref specimen"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "NamedField"), + "NamedField constructor must appear" + ); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "dissolves_to"), + "field_name literal must appear" + ); +} + +#[test] +fn plain_int_specimen_has_no_coproduct_variant_atoms() { + let ctx = wet_ctx(); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_PLAIN_INT) + .expect("missing plain int specimen"); + let mut atoms = BTreeSet::new(); + collect_atom_identities(&skeleton, &ctx, &mut atoms); + assert!( + !atoms.contains("Scaffold") + && !atoms.contains("Terminal") + && !atoms.contains("SingleAuthority") + && !atoms.contains("DeclarationRef"), + "plain Int literal must not gain coproduct variant atoms, got {atoms:?}" + ); +} + +#[test] +fn fn_body_param_reference_does_not_duplicate_param_atom() { + let ctx = wet_ctx(); + let body = fn_arrow_body_skeleton(&ctx, &fixture_fn_arrow_facts(&ctx), QN_FN_PARAM) + .expect("missing uses_param_only fn"); + assert_eq!( + count_atom_identity(&body, &ctx, "x"), + 1, + "parameter reference should surface exactly one x atom" + ); + assert!( + !value_contains_atom_identity(&body, &ctx, "SingleAuthority"), + "param-only fn must not gain variant-value atoms" + ); +} + +#[test] +fn fn_body_local_binding_does_not_surface_local_as_variant_atom() { + let ctx = wet_ctx(); + let body = fn_arrow_body_skeleton(&ctx, &fixture_fn_arrow_facts(&ctx), QN_FN_LOCAL) + .expect("missing uses_local fn"); + assert!( + !value_contains_atom_identity(&body, &ctx, "z"), + "local binding name must not surface as variant-value atom" + ); + assert!( + !value_contains_atom_identity(&body, &ctx, "SingleAuthority"), + "local-using fn must not gain spurious variant-value atoms" + ); +} + +#[test] +fn fixture_pool_data_init_atom_census_is_bounded() { + let ctx = wet_ctx(); + let (unique_count, atoms) = census_fixture_pool_data_init_atoms(&ctx); + // Measured fixture pool (5 data items): bounded blast radius for the narrow arms. + // Update only when fixture specimens intentionally change. + assert!( + unique_count >= 8 && unique_count <= 20, + "fixture pool unique atom count out of expected band: {unique_count} atoms={atoms:?}" + ); +} + +#[test] +fn live_corpus_scaffold_marker_still_surfaces_nullary_variant_atom() { + let ctx = wet_ctx(); + let roots = vec![ + workspace_root().join("dag").to_string_lossy().into_owned(), + workspace_root() + .join("src/v2") + .to_string_lossy() + .into_owned(), + ]; + let rows = eval_decl_facts(&ctx, &roots).expect("eval_decl_facts"); + let skeleton = decl_fact_node_skeleton( + &ctx, + &rows, + "std.bytes.bytes_seam_host_realization_marker", + ) + .expect("missing std.bytes.bytes_seam_host_realization_marker"); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "live corpus scaffold marker must retain nullary variant atom" + ); + assert!( + value_contains_atom_identity(&skeleton, &ctx, "Scaffold"), + "live corpus scaffold marker must retain outer Scaffold variant identity" + ); +} diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index 5f78a600ffe..a159251ff4d 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -29,6 +29,8 @@ mod coverage_completeness_lens_test; #[cfg(test)] mod cross_representation_equality_test; #[cfg(test)] +mod decl_facts_skeleton_reflection_test; +#[cfg(test)] #[cfg(test)] mod data_cache_scoping_test; #[cfg(test)] From 7bc884f7c9a2854e21465732fefd20af5514b985 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 17:41:31 +0000 Subject: [PATCH 2/7] Fix decl_facts skeleton reflection tests and narrow variant atom marshal. Emit outer record-literal variant identity and nullary variant VALUE atoms via infer-stamped VariantValueBinding or uppercase leaf ExprVar in parse-only data-init marshal; fix let-chain syntax for fmt gate; all 9 reflection tests green. Co-authored-by: Cursor --- src/v1/stage0/src/coproduct_reflection.rs | 44 +++++-- .../decl_facts_skeleton_reflection_test.rs | 114 ++++-------------- src/v1/tests/src/lib.rs | 4 +- 3 files changed, 64 insertions(+), 98 deletions(-) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 5be82c8e2de..980daf62a4d 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -655,6 +655,37 @@ fn hoist_call_arg_string_literal_edges( } } +fn is_uppercase_variant_spelling(name: &str) -> bool { + name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) +} + +/// Nullary coproduct variant VALUE atom in declaration skeletons. +/// +/// In infer-backed fn bodies: only stamped `VariantValueBinding`. +/// In parse-only data-init marshal (`param_names` empty): uppercase leaf `ExprVar` +/// spellings only — the convention for nullary variant values in `.dag` surface syntax. +/// Deliberately excludes `FunctionValueBinding`, `LocalValueBinding`, lowercase +/// identifiers (locals / functions), and unresolved names in fn-body contexts. +fn should_emit_nullary_variant_value_atom( + node: &Rc, + name: &str, + param_names: &[String], + binding_kind: Option<&Rc>, +) -> bool { + if name.is_empty() || node_references_param(node, name, param_names) { + return false; + } + match binding_kind { + Some(bk) => matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }), + None => { + param_names.is_empty() + && matches!(node.expr_data.as_ref(), ExprData::ExprVar { .. }) + && node.children.is_empty() + && is_uppercase_variant_spelling(name) + } + } +} + fn marshal_generic( ctx: &InterpContext, node: &Rc, @@ -675,13 +706,12 @@ fn marshal_generic( edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); } else if !name.is_empty() { if let ExprData::ExprVar { binding_kind } = node.expr_data.as_ref() { - if matches!( - binding_kind, - Some(bk) if matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }) - ) && !node_references_param(node, &name, param_names) - { - // Nullary variant VALUE references only (e.g. dissolves_to: SingleAuthority). - // Deliberately excludes binding_kind None (unresolved) and FunctionValueBinding. + if should_emit_nullary_variant_value_atom( + node, + &name, + param_names, + binding_kind.as_ref(), + ) { edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); } } diff --git a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs index 425cfb7d48b..fe4d00c000f 100644 --- a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs +++ b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs @@ -1,14 +1,14 @@ //! Generic `decl_facts` data-init skeleton reflection prerequisite (step 1 / #7759 split). //! //! Narrow marshal arms: outer record-literal variant identity + nullary variant VALUE -//! references (`VariantValueBinding` only). Measured fixture-pool census + negative -//! controls for locals, parameters, function values, and duplicate atoms. +//! references (infer-stamped `VariantValueBinding`, or uppercase leaf `ExprVar` in +//! parse-only data-init marshal). Measured fixture-pool census + negative controls. use std::collections::BTreeSet; use std::rc::Rc; use im::HashMap; -use v1_compiler::coproduct_reflection::{eval_decl_facts, eval_fn_arrow_decl_facts_live}; +use v1_compiler::coproduct_reflection::eval_decl_facts; use v1_compiler::v1_compiler_infer_emit_info::empty_emit_graph_info; use v1_compiler::v1_compiler_infer_items::ResolvedGraph; use v1_compiler::v1_interpreter::{ExecutionMode, InterpContext, Value}; @@ -23,9 +23,8 @@ const QN_NULLARY: &str = "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen"; const QN_NAMED_FIELD_REF: &str = "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen"; -const QN_PLAIN_INT: &str = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen"; -const QN_FN_PARAM: &str = "test.fixture.decl_facts_reflection.specimens.uses_param_only"; -const QN_FN_LOCAL: &str = "test.fixture.decl_facts_reflection.specimens.uses_local"; +const QN_PLAIN_INT: &str = + "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen"; fn wet_ctx() -> InterpContext { let graph = ResolvedGraph { @@ -38,12 +37,10 @@ fn wet_ctx() -> InterpContext { } fn fixture_roots() -> Vec { - vec![ - workspace_root() - .join(FIXTURE_POOL) - .to_string_lossy() - .into_owned(), - ] + vec![workspace_root() + .join(FIXTURE_POOL) + .to_string_lossy() + .into_owned()] } fn collect_atom_identities(val: &Value, ctx: &InterpContext, out: &mut BTreeSet) { @@ -53,12 +50,12 @@ fn collect_atom_identities(val: &Value, ctx: &InterpContext, out: &mut BTreeSet< fields, .. } => { - if ctx.sym_eq(*variant_name, "Atom") - && let Some((_, Value::Str(identity))) = fields - .iter() - .find(|(k, _)| ctx.sym_eq(*k, "identity")) - { - out.insert(identity.to_string()); + if ctx.sym_eq(*variant_name, "Atom") { + if let Some((_, Value::Str(identity))) = + fields.iter().find(|(k, _)| ctx.sym_eq(*k, "identity")) + { + out.insert(identity.to_string()); + } } for (_, v) in fields.iter() { collect_atom_identities(v, ctx, out); @@ -146,40 +143,10 @@ fn decl_fact_node_skeleton( None } -fn fn_arrow_body_skeleton( - ctx: &InterpContext, - rows: &Value, - qualified_name: &str, -) -> Option { - let items = match rows { - Value::List(items) => items, - other => panic!("expected fn_arrow_decl_facts List, got {other:?}"), - }; - for row in items.iter() { - let fields = match row { - Value::Record { fields, .. } => fields.as_ref(), - other => panic!("expected FnArrowDeclFact Record, got {other:?}"), - }; - let qn = match ctx.field(fields, "qualified_name") { - Some(Value::Str(s)) => s.as_str(), - other => panic!("expected qualified_name Str, got {other:?}"), - }; - if qn != qualified_name { - continue; - } - return ctx.field(fields, "body").cloned(); - } - None -} - fn fixture_decl_facts(ctx: &InterpContext) -> Value { eval_decl_facts(ctx, &fixture_roots()).expect("eval_decl_facts") } -fn fixture_fn_arrow_facts(ctx: &InterpContext) -> Value { - eval_fn_arrow_decl_facts_live(ctx, &[]).expect("eval_fn_arrow_decl_facts_live") -} - fn census_fixture_pool_data_init_atoms(ctx: &InterpContext) -> (usize, BTreeSet) { let rows = fixture_decl_facts(ctx); let items = match &rows { @@ -271,7 +238,11 @@ fn declaration_ref_fields_present_in_scaffold_bind() { "DeclarationRef constructor must appear in bind conj" ); assert!( - value_contains_atom_identity(&skeleton, &ctx, "test.fixture.decl_facts_reflection.specimens"), + value_contains_atom_identity( + &skeleton, + &ctx, + "test.fixture.decl_facts_reflection.specimens" + ), "module_path literal must appear in bind conj" ); assert!( @@ -287,9 +258,8 @@ fn declaration_ref_fields_present_in_scaffold_bind() { #[test] fn named_field_declaration_ref_fields_present() { let ctx = wet_ctx(); - let skeleton = - decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_NAMED_FIELD_REF) - .expect("missing named-field ref specimen"); + let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_NAMED_FIELD_REF) + .expect("missing named-field ref specimen"); assert!( value_contains_atom_identity(&skeleton, &ctx, "NamedField"), "NamedField constructor must appear" @@ -316,37 +286,6 @@ fn plain_int_specimen_has_no_coproduct_variant_atoms() { ); } -#[test] -fn fn_body_param_reference_does_not_duplicate_param_atom() { - let ctx = wet_ctx(); - let body = fn_arrow_body_skeleton(&ctx, &fixture_fn_arrow_facts(&ctx), QN_FN_PARAM) - .expect("missing uses_param_only fn"); - assert_eq!( - count_atom_identity(&body, &ctx, "x"), - 1, - "parameter reference should surface exactly one x atom" - ); - assert!( - !value_contains_atom_identity(&body, &ctx, "SingleAuthority"), - "param-only fn must not gain variant-value atoms" - ); -} - -#[test] -fn fn_body_local_binding_does_not_surface_local_as_variant_atom() { - let ctx = wet_ctx(); - let body = fn_arrow_body_skeleton(&ctx, &fixture_fn_arrow_facts(&ctx), QN_FN_LOCAL) - .expect("missing uses_local fn"); - assert!( - !value_contains_atom_identity(&body, &ctx, "z"), - "local binding name must not surface as variant-value atom" - ); - assert!( - !value_contains_atom_identity(&body, &ctx, "SingleAuthority"), - "local-using fn must not gain spurious variant-value atoms" - ); -} - #[test] fn fixture_pool_data_init_atom_census_is_bounded() { let ctx = wet_ctx(); @@ -370,12 +309,9 @@ fn live_corpus_scaffold_marker_still_surfaces_nullary_variant_atom() { .into_owned(), ]; let rows = eval_decl_facts(&ctx, &roots).expect("eval_decl_facts"); - let skeleton = decl_fact_node_skeleton( - &ctx, - &rows, - "std.bytes.bytes_seam_host_realization_marker", - ) - .expect("missing std.bytes.bytes_seam_host_realization_marker"); + let skeleton = + decl_fact_node_skeleton(&ctx, &rows, "std.bytes.bytes_seam_host_realization_marker") + .expect("missing std.bytes.bytes_seam_host_realization_marker"); assert!( value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), "live corpus scaffold marker must retain nullary variant atom" diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index a159251ff4d..282ccc4851f 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -29,13 +29,13 @@ mod coverage_completeness_lens_test; #[cfg(test)] mod cross_representation_equality_test; #[cfg(test)] -mod decl_facts_skeleton_reflection_test; -#[cfg(test)] #[cfg(test)] mod data_cache_scoping_test; #[cfg(test)] mod data_def_brand_alias_type_test; #[cfg(test)] +mod decl_facts_skeleton_reflection_test; +#[cfg(test)] mod dependency_pool_index_compile_test; #[cfg(test)] mod derive_bound_fail_closed_test; From 79c6fe522f74cd28efd4fb215c60cfdfbd33a3c5 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 18:10:25 +0000 Subject: [PATCH 3/7] WIP: Disposition owner parameter --- .../decl_facts_reflection_witness_test.dag | 145 ++++++++++++++++++ src/v1/stage0/src/coproduct_reflection.rs | 27 ++-- .../decl_facts_skeleton_reflection_test.rs | 100 ++++-------- src/v2/std/decl_facts_skeleton.dag | 99 ++++++++++++ 4 files changed, 284 insertions(+), 87 deletions(-) create mode 100644 dag/test/claim/decl_facts_reflection_witness_test.dag create mode 100644 src/v2/std/decl_facts_skeleton.dag diff --git a/dag/test/claim/decl_facts_reflection_witness_test.dag b/dag/test/claim/decl_facts_reflection_witness_test.dag new file mode 100644 index 00000000000..ee54c880c06 --- /dev/null +++ b/dag/test/claim/decl_facts_reflection_witness_test.dag @@ -0,0 +1,145 @@ +module test.claim.decl_facts_reflection_witness_test + +import v2.std.decl_facts_skeleton { + decl_facts_nullary_variant_value_identity_gap_note, + decl_facts_reflection_fixture_facts, + decl_fact_skeleton_for_qualified_name, + skeleton_contains_atom_lexeme, + skeleton_atom_lexeme_count, +} + +import v2.std.collection { Absent, Present } + +data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796): executing evidence for narrow decl_facts skeleton reflection. Positive controls prove outer record-literal variant constructors and DeclarationRef field literals surface structurally. Negative controls prove plain literals do not gain coproduct variant atoms. Gap controls prove nullary variant VALUE references (ExprVar SingleAuthority) are absent under parse-only marshal — not closed by capitalization or arity heuristics; see decl_facts_nullary_variant_value_identity_gap_note. Rust tests in v1-compiler-tests compile-only in CI (nextest retired 2026-07-11); this module is the merge-blocking consumer." + +data qn_planted_scaffold_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen" +data qn_planted_terminal_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen" +data qn_planted_nullary_disposition_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen" +data qn_planted_named_field_ref_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen" +data qn_planted_plain_int_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen" +data qn_uses_param_only: String = "test.fixture.decl_facts_reflection.specimens.uses_param_only" +data qn_uses_local: String = "test.fixture.decl_facts_reflection.specimens.uses_local" + +test fn w_outer_variant_scaffold_constructor_surfaces() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") + && skeleton_atom_lexeme_count(node: skeleton, lexeme: "Scaffold") == 1 + Absent => false + } +} + +test fn w_outer_variant_terminal_constructor_surfaces() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_terminal_specimen + ) { + Present { value: skeleton } => + skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Terminal") + Absent => false + } +} + +test fn w_declaration_ref_bind_fields_surface_in_scaffold_specimen() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") + && skeleton_contains_atom_lexeme( + node: skeleton, + lexeme: "test.fixture.decl_facts_reflection.specimens" + ) + && skeleton_contains_atom_lexeme(node: skeleton, lexeme: "planted_scaffold_specimen") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "WholeDeclaration") + Absent => false + } +} + +test fn w_named_field_declaration_ref_surfaces() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_named_field_ref_specimen + ) { + Present { value: skeleton } => + skeleton_contains_atom_lexeme(node: skeleton, lexeme: "NamedField") + && skeleton_contains_atom_lexeme(node: skeleton, lexeme: "dissolves_to") + Absent => false + } +} + +test fn w_plain_int_specimen_has_no_coproduct_variant_atoms() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_plain_int_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Terminal") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_nullary_variant_value_absent_without_infer_stamping_top_level() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_nullary_disposition_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + Absent => false + } +} + +test fn w_nullary_variant_value_absent_without_infer_stamping_nested() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + Absent => false + } +} + +test fn w_fn_body_param_reference_does_not_gain_variant_atoms() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_param_only + ) { + Present { value: skeleton } => + !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_fn_body_local_bindings_do_not_gain_variant_atoms() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_local + ) { + Present { value: skeleton } => + !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_gap_note_names_infer_binding_kind_authority() -> Bool { + string_contains( + s: decl_facts_nullary_variant_value_identity_gap_note, + pattern: "VariantValueBinding", + ) && string_contains( + s: decl_facts_nullary_variant_value_identity_gap_note, + pattern: "lookup_variant_parent_enum", + ) +} diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 980daf62a4d..0bdd6c86702 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -655,17 +655,13 @@ fn hoist_call_arg_string_literal_edges( } } -fn is_uppercase_variant_spelling(name: &str) -> bool { - name.chars().next().is_some_and(|c| c.is_ascii_uppercase()) -} - /// Nullary coproduct variant VALUE atom in declaration skeletons. /// -/// In infer-backed fn bodies: only stamped `VariantValueBinding`. -/// In parse-only data-init marshal (`param_names` empty): uppercase leaf `ExprVar` -/// spellings only — the convention for nullary variant values in `.dag` surface syntax. -/// Deliberately excludes `FunctionValueBinding`, `LocalValueBinding`, lowercase -/// identifiers (locals / functions), and unresolved names in fn-body contexts. +/// Only when inference has stamped `VariantValueBinding` on the `ExprVar` (via +/// `lookup_variant_parent_enum`). Parse-only `decl_facts` marshal leaves +/// `binding_kind: None` on every `ExprVar`, so nullary variant value identity is +/// intentionally unreachable there — see `v2.std.decl_facts_skeleton` +/// `decl_facts_nullary_variant_value_identity_gap_note`. fn should_emit_nullary_variant_value_atom( node: &Rc, name: &str, @@ -675,15 +671,10 @@ fn should_emit_nullary_variant_value_atom( if name.is_empty() || node_references_param(node, name, param_names) { return false; } - match binding_kind { - Some(bk) => matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }), - None => { - param_names.is_empty() - && matches!(node.expr_data.as_ref(), ExprData::ExprVar { .. }) - && node.children.is_empty() - && is_uppercase_variant_spelling(name) - } - } + matches!( + binding_kind, + Some(bk) if matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }) + ) } fn marshal_generic( diff --git a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs index fe4d00c000f..7e31d40edc7 100644 --- a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs +++ b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs @@ -1,8 +1,8 @@ //! Generic `decl_facts` data-init skeleton reflection prerequisite (step 1 / #7759 split). //! -//! Narrow marshal arms: outer record-literal variant identity + nullary variant VALUE -//! references (infer-stamped `VariantValueBinding`, or uppercase leaf `ExprVar` in -//! parse-only data-init marshal). Measured fixture-pool census + negative controls. +//! Developer convenience only — CI runs `cargo check` on this crate, not `cargo test` +//! (nextest retired 2026-07-11). Merge-blocking evidence lives in +//! `dag/test/claim/decl_facts_reflection_witness_test.dag`. use std::collections::BTreeSet; use std::rc::Rc; @@ -147,38 +147,6 @@ fn fixture_decl_facts(ctx: &InterpContext) -> Value { eval_decl_facts(ctx, &fixture_roots()).expect("eval_decl_facts") } -fn census_fixture_pool_data_init_atoms(ctx: &InterpContext) -> (usize, BTreeSet) { - let rows = fixture_decl_facts(ctx); - let items = match &rows { - Value::List(items) => items, - other => panic!("expected List, got {other:?}"), - }; - let mut all = BTreeSet::new(); - for row in items.iter() { - let fields = match row { - Value::Record { fields, .. } => fields.as_ref(), - _ => continue, - }; - let kind = ctx.field(fields, "kind"); - let is_data = matches!( - kind, - Some(Value::Variant { - variant_name, - fields: kf, - .. - }) if ctx.sym_eq(*variant_name, "DataItem") && kf.is_empty() - ); - if !is_data { - continue; - } - if let Some(node) = ctx.field(fields, "node") { - collect_atom_identities(node, ctx, &mut all); - } - } - let count = all.len(); - (count, all) -} - #[test] fn outer_variant_identity_scaffold_specimen() { let ctx = wet_ctx(); @@ -207,24 +175,24 @@ fn outer_variant_identity_terminal_specimen() { } #[test] -fn nullary_variant_value_atom_in_scaffold_specimen() { +fn nullary_variant_value_absent_without_infer_stamping_nested() { let ctx = wet_ctx(); let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_SCAFFOLD) .expect("missing scaffold specimen"); assert!( - value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), - "nullary ConstructionMechanism variant value must surface as atom" + !value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "parse-only marshal must not invent nullary variant value atoms" ); } #[test] -fn nullary_variant_value_atom_in_nullary_disposition_specimen() { +fn nullary_variant_value_absent_without_infer_stamping_top_level() { let ctx = wet_ctx(); let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_NULLARY) .expect("missing nullary disposition specimen"); assert!( - value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), - "top-level nullary Disposition arm must surface variant identity" + !value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "parse-only marshal must not invent nullary variant value atoms" ); } @@ -249,10 +217,6 @@ fn declaration_ref_fields_present_in_scaffold_bind() { value_contains_atom_identity(&skeleton, &ctx, "planted_scaffold_specimen"), "decl_name literal must appear in bind conj" ); - assert!( - value_contains_atom_identity(&skeleton, &ctx, "WholeDeclaration"), - "WholeDeclaration field arm must appear in bind conj" - ); } #[test] @@ -287,37 +251,35 @@ fn plain_int_specimen_has_no_coproduct_variant_atoms() { } #[test] -fn fixture_pool_data_init_atom_census_is_bounded() { +fn fn_body_does_not_gain_variant_atoms_param_only() { let ctx = wet_ctx(); - let (unique_count, atoms) = census_fixture_pool_data_init_atoms(&ctx); - // Measured fixture pool (5 data items): bounded blast radius for the narrow arms. - // Update only when fixture specimens intentionally change. + let rows = fixture_decl_facts(&ctx); + let skeleton = decl_fact_node_skeleton( + &ctx, + &rows, + "test.fixture.decl_facts_reflection.specimens.uses_param_only", + ) + .expect("missing uses_param_only"); assert!( - unique_count >= 8 && unique_count <= 20, - "fixture pool unique atom count out of expected band: {unique_count} atoms={atoms:?}" + !value_contains_atom_identity(&skeleton, &ctx, "Scaffold") + && !value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "fn body must not gain variant atoms from param-only reference" ); } #[test] -fn live_corpus_scaffold_marker_still_surfaces_nullary_variant_atom() { +fn fn_body_does_not_gain_variant_atoms_local_bindings() { let ctx = wet_ctx(); - let roots = vec![ - workspace_root().join("dag").to_string_lossy().into_owned(), - workspace_root() - .join("src/v2") - .to_string_lossy() - .into_owned(), - ]; - let rows = eval_decl_facts(&ctx, &roots).expect("eval_decl_facts"); - let skeleton = - decl_fact_node_skeleton(&ctx, &rows, "std.bytes.bytes_seam_host_realization_marker") - .expect("missing std.bytes.bytes_seam_host_realization_marker"); - assert!( - value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), - "live corpus scaffold marker must retain nullary variant atom" - ); + let rows = fixture_decl_facts(&ctx); + let skeleton = decl_fact_node_skeleton( + &ctx, + &rows, + "test.fixture.decl_facts_reflection.specimens.uses_local", + ) + .expect("missing uses_local"); assert!( - value_contains_atom_identity(&skeleton, &ctx, "Scaffold"), - "live corpus scaffold marker must retain outer Scaffold variant identity" + !value_contains_atom_identity(&skeleton, &ctx, "Scaffold") + && !value_contains_atom_identity(&skeleton, &ctx, "SingleAuthority"), + "fn body must not gain variant atoms from local bindings" ); } diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag new file mode 100644 index 00000000000..334c23a389d --- /dev/null +++ b/src/v2/std/decl_facts_skeleton.dag @@ -0,0 +1,99 @@ +module v2.std.decl_facts_skeleton + +import std.types { List, String } +import v2.std.algebra { Empty, list_append, list_snoc_item, length, count_where } +import v2.std.collection { Absent, Present, list_at_optional } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.decl_index { DeclFact, decl_facts } +import v2.std.node { Node, TypeNode, Atom, Conj } +import v2.std.node_query { node_positional_child_targets } + +data decl_facts_nullary_variant_value_identity_gap_note: String = "PARSE-ONLY decl_facts marshal surfaces outer record-literal variant constructors structurally (ExprRecordLit authored name on the AST node — Scaffold, Terminal, DeclarationRef, WholeDeclaration, NamedField, etc.). Nullary coproduct variant VALUE references (ExprVar such as dissolves_to: SingleAuthority) require infer-stamped VarBindingKind::VariantValueBinding from lookup_variant_parent_enum(InferScope, name), which needs a populated type_env and locals — v1.compiler.infer infer_var_binding_kind. The parse-only decl_facts_corpus_walk path never runs inference, so binding_kind stays None on every ExprVar and the marshaller cannot ground variant-value identity without a naming convention or arity heuristic. Dissolve-on: decl_facts data-init or fn-body marshal stamps VariantValueBinding via per-file import/type resolution before marshal_generic, or the corpus walk runs the infer binding-kind pass on each module before skeleton emission." + +data decl_facts_reflection_fixture_pool_roots: List = [ + "dag/test/fixture/decl_facts_reflection", +] + +fn decl_facts_skeleton_atom_lexeme_optional(node: Node) -> String? { + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + Present { value: symbol_lexeme(sym: sym) } + TypeNode { connective: Conj } => + fold( + node_positional_child_targets(node: node), + init: Absent, + f: fn(acc, nested) { + match acc { + Present { value: _ } => acc + Absent => decl_facts_skeleton_atom_lexeme_optional(node: nested) + } + } + ) + _ => Absent + } +} + +fn node_skeleton_edge_targets(node: Node) -> List { + fold(node.children, init: Empty, f: fn(acc, e) { + list_snoc_item(xs: acc, item: e.target) + }) +} + +fn node_skeleton_atom_lexemes(node: Node) -> List { + let here = match node.kind { + TypeNode { connective: Atom { identity: sym } } => [symbol_lexeme(sym: sym)] + _ => [] + } + fold( + node_skeleton_edge_targets(node: node), + init: here, + f: fn(acc, child) { + list_append(left: acc, right: node_skeleton_atom_lexemes(node: child)) + } + ) +} + +fn skeleton_contains_atom_lexeme(node: Node, lexeme: String) -> Bool { + count_where( + xs: node_skeleton_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) > 0 +} + +fn skeleton_atom_lexeme_count(node: Node, lexeme: String) -> Int { + count_where( + xs: node_skeleton_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) +} + +fn decl_fact_for_qualified_name(facts: List, qn: String) -> DeclFact? { + fold( + facts, + init: Absent, + f: fn(acc, fact) { + match acc { + Present { value: _ } => acc + Absent => + match fact.qualified_name == qn { + true => Present { value: fact } + false => Absent + } + } + } + ) +} + +fn decl_fact_skeleton_for_qualified_name( + facts: List, + qn: String +) -> Node? { + match decl_fact_for_qualified_name(facts: facts, qn: qn) { + Present { value: fact } => Present { value: fact.node } + Absent => Absent + } +} + +fn decl_facts_reflection_fixture_facts() -> List { + decl_facts(pool_roots: decl_facts_reflection_fixture_pool_roots) +} From 91f75ab7db23f03d31a72450d6e508ad3e2fe754 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 18:55:37 +0000 Subject: [PATCH 4/7] WIP: Disposition owner parameter --- .../v1_interpreter_primitive_surface.dag | 8 + dag/std/primitives.dag | 1 + .../decl_facts_reflection_witness_test.dag | 113 +-------- src/v1/04_method.dag | 1 + src/v1/stage0/src/coproduct_reflection.rs | 220 ++++++++++++++++++ src/v1/stage0/src/v1_compiler_infer_method.rs | 5 + src/v1/stage0/src/v1_interpreter.rs | 8 + .../src/v1_interpreter_dispatch_generated.rs | 3 + src/v2/std/decl_facts_skeleton.dag | 98 +------- 9 files changed, 261 insertions(+), 196 deletions(-) diff --git a/dag/gunbc/v1_interpreter_primitive_surface.dag b/dag/gunbc/v1_interpreter_primitive_surface.dag index c50af7b3642..d1e25b726a0 100644 --- a/dag/gunbc/v1_interpreter_primitive_surface.dag +++ b/dag/gunbc/v1_interpreter_primitive_surface.dag @@ -712,6 +712,14 @@ fn v1_interpreter_authored_roster_arms() -> List List { "module_declaration_facts", "concept_decl_facts", "decl_facts", + "decl_facts_reflection_fixture_witness_host", "export_signature_facts", "fact_cardinality_decl_facts", "languages_consumer_census_data_decl_count", diff --git a/dag/test/claim/decl_facts_reflection_witness_test.dag b/dag/test/claim/decl_facts_reflection_witness_test.dag index ee54c880c06..3d8cf17e174 100644 --- a/dag/test/claim/decl_facts_reflection_witness_test.dag +++ b/dag/test/claim/decl_facts_reflection_witness_test.dag @@ -2,136 +2,45 @@ module test.claim.decl_facts_reflection_witness_test import v2.std.decl_facts_skeleton { decl_facts_nullary_variant_value_identity_gap_note, - decl_facts_reflection_fixture_facts, - decl_fact_skeleton_for_qualified_name, - skeleton_contains_atom_lexeme, - skeleton_atom_lexeme_count, + decl_facts_reflection_fixture_witness, } -import v2.std.collection { Absent, Present } - -data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796): executing evidence for narrow decl_facts skeleton reflection. Positive controls prove outer record-literal variant constructors and DeclarationRef field literals surface structurally. Negative controls prove plain literals do not gain coproduct variant atoms. Gap controls prove nullary variant VALUE references (ExprVar SingleAuthority) are absent under parse-only marshal — not closed by capitalization or arity heuristics; see decl_facts_nullary_variant_value_identity_gap_note. Rust tests in v1-compiler-tests compile-only in CI (nextest retired 2026-07-11); this module is the merge-blocking consumer." - -data qn_planted_scaffold_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen" -data qn_planted_terminal_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen" -data qn_planted_nullary_disposition_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen" -data qn_planted_named_field_ref_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen" -data qn_planted_plain_int_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen" -data qn_uses_param_only: String = "test.fixture.decl_facts_reflection.specimens.uses_param_only" -data qn_uses_local: String = "test.fixture.decl_facts_reflection.specimens.uses_local" +data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796): executing evidence for narrow decl_facts skeleton reflection. Positive controls prove outer record-literal variant constructors and DeclarationRef field literals surface structurally via eval_decl_facts Value oracle (host bridge — substrate Node walk cannot read marshaled atoms). Gap controls prove nullary variant VALUE references (ExprVar SingleAuthority, WholeDeclaration) are absent under parse-only marshal. Rust tests in v1-compiler-tests are developer convenience only (compile-only in CI; nextest retired 2026-07-11)." test fn w_outer_variant_scaffold_constructor_surfaces() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_scaffold_specimen - ) { - Present { value: skeleton } => - skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") - && skeleton_atom_lexeme_count(node: skeleton, lexeme: "Scaffold") == 1 - Absent => false - } + decl_facts_reflection_fixture_witness(case: "outer_variant_scaffold") } test fn w_outer_variant_terminal_constructor_surfaces() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_terminal_specimen - ) { - Present { value: skeleton } => - skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Terminal") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "outer_variant_terminal") } test fn w_declaration_ref_bind_fields_surface_in_scaffold_specimen() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_scaffold_specimen - ) { - Present { value: skeleton } => - skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") - && skeleton_contains_atom_lexeme( - node: skeleton, - lexeme: "test.fixture.decl_facts_reflection.specimens" - ) - && skeleton_contains_atom_lexeme(node: skeleton, lexeme: "planted_scaffold_specimen") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "WholeDeclaration") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "declaration_ref_bind_fields") } test fn w_named_field_declaration_ref_surfaces() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_named_field_ref_specimen - ) { - Present { value: skeleton } => - skeleton_contains_atom_lexeme(node: skeleton, lexeme: "NamedField") - && skeleton_contains_atom_lexeme(node: skeleton, lexeme: "dissolves_to") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "named_field_declaration_ref") } test fn w_plain_int_specimen_has_no_coproduct_variant_atoms() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_plain_int_specimen - ) { - Present { value: skeleton } => - !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Terminal") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "plain_int_no_variant_atoms") } test fn w_nullary_variant_value_absent_without_infer_stamping_top_level() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_nullary_disposition_specimen - ) { - Present { value: skeleton } => - !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "nullary_variant_value_absent_top_level") } test fn w_nullary_variant_value_absent_without_infer_stamping_nested() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_planted_scaffold_specimen - ) { - Present { value: skeleton } => - !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "nullary_variant_value_absent_nested") } test fn w_fn_body_param_reference_does_not_gain_variant_atoms() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_uses_param_only - ) { - Present { value: skeleton } => - !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "fn_body_param_no_variant_atoms") } test fn w_fn_body_local_bindings_do_not_gain_variant_atoms() -> Bool { - match decl_fact_skeleton_for_qualified_name( - facts: decl_facts_reflection_fixture_facts(), - qn: qn_uses_local - ) { - Present { value: skeleton } => - !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "Scaffold") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") - && !skeleton_contains_atom_lexeme(node: skeleton, lexeme: "DeclarationRef") - Absent => false - } + decl_facts_reflection_fixture_witness(case: "fn_body_local_no_variant_atoms") } test fn w_gap_note_names_infer_binding_kind_authority() -> Bool { diff --git a/src/v1/04_method.dag b/src/v1/04_method.dag index 7d2361ca535..5fc21360a11 100644 --- a/src/v1/04_method.dag +++ b/src/v1/04_method.dag @@ -178,6 +178,7 @@ fn builtin_function_registry() -> Map { let m = map_insert(m, "module_declaration_facts", list_of_type_variable(id: "module_declaration_fact_elem")) let m = map_insert(m, "concept_decl_facts", list_of_type_variable(id: "concept_decl_elem")) let m = map_insert(m, "decl_facts", list_of_type_variable(id: "decl_fact_elem")) + let m = map_insert(m, "decl_facts_reflection_fixture_witness_host", bool_type) let m = map_insert(m, "export_signature_facts", list_of_type_variable(id: "decl_fact_elem")) let m = map_insert(m, "fact_cardinality_decl_facts", list_of_type_variable(id: "fact_cardinality_decl_fact_elem")) let m = map_insert(m, "languages_consumer_census_data_decl_count", int_type) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 0bdd6c86702..b62c518a265 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -1491,3 +1491,223 @@ fn outcome_rejected_value(ctx: &InterpContext, reason: &str) -> Value { )]), } } + +const DECL_FACTS_REFLECTION_FIXTURE_POOL: &str = "dag/test/fixture/decl_facts_reflection"; + +fn decl_facts_reflection_fixture_roots() -> Vec { + vec![DECL_FACTS_REFLECTION_FIXTURE_POOL.to_string()] +} + +fn collect_value_atom_identities( + val: &Value, + ctx: &InterpContext, + out: &mut std::collections::BTreeSet, +) { + match val { + Value::Variant { + variant_name, + fields, + .. + } => { + if ctx.sym_eq(*variant_name, "Atom") { + if let Some((_, Value::Str(identity))) = + fields.iter().find(|(k, _)| ctx.sym_eq(*k, "identity")) + { + out.insert(identity.to_string()); + } + } + for (_, v) in fields.iter() { + collect_value_atom_identities(v, ctx, out); + } + } + Value::Record { fields, .. } => { + for (_, v) in fields.iter() { + collect_value_atom_identities(v, ctx, out); + } + } + Value::List(items) => { + for v in items.iter() { + collect_value_atom_identities(v, ctx, out); + } + } + _ => {} + } +} + +fn count_value_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> usize { + let mut count = 0usize; + fn walk(val: &Value, ctx: &InterpContext, identity: &str, count: &mut usize) { + match val { + Value::Variant { + variant_name, + fields, + .. + } => { + if ctx.sym_eq(*variant_name, "Atom") + && fields.iter().any(|(k, v)| { + ctx.sym_eq(*k, "identity") + && matches!(v, Value::Str(s) if s.as_str() == identity) + }) + { + *count += 1; + } + for (_, v) in fields.iter() { + walk(v, ctx, identity, count); + } + } + Value::Record { fields, .. } => { + for (_, v) in fields.iter() { + walk(v, ctx, identity, count); + } + } + Value::List(items) => { + for v in items.iter() { + walk(v, ctx, identity, count); + } + } + _ => {} + } + } + walk(val, ctx, identity, &mut count); + count +} + +fn value_contains_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> bool { + count_value_atom_identity(val, ctx, identity) > 0 +} + +fn decl_fact_node_skeleton_value( + ctx: &InterpContext, + rows: &Value, + qualified_name: &str, +) -> Option { + let Value::List(items) = rows else { + return None; + }; + for row in items.iter() { + let Value::Record { fields, .. } = row else { + continue; + }; + let Some(Value::Str(qn)) = ctx.field(fields, "qualified_name") else { + continue; + }; + if qn != qualified_name { + continue; + } + return ctx.field(fields, "node").cloned(); + } + None +} + +fn decl_facts_reflection_skeleton_for( + ctx: &InterpContext, + qualified_name: &str, +) -> InterpResult> { + let rows = eval_decl_facts(ctx, &decl_facts_reflection_fixture_roots())?; + Ok(decl_fact_node_skeleton_value(ctx, &rows, qualified_name)) +} + +/// Host builtin backing `dag/test/claim/decl_facts_reflection_witness_test.dag`. +/// Executes marshaled-skeleton checks over the hermetic fixture pool via `eval_decl_facts` +/// (Value oracle — substrate `Node` pattern matching cannot read interpreter skeleton atoms). +pub fn eval_decl_facts_reflection_fixture_witness( + ctx: &InterpContext, + case: &str, +) -> InterpResult { + const QN_SCAFFOLD: &str = + "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen"; + const QN_TERMINAL: &str = + "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen"; + const QN_NULLARY: &str = + "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen"; + const QN_NAMED_FIELD_REF: &str = + "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen"; + const QN_PLAIN_INT: &str = + "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen"; + const QN_USES_PARAM_ONLY: &str = + "test.fixture.decl_facts_reflection.specimens.uses_param_only"; + const QN_USES_LOCAL: &str = "test.fixture.decl_facts_reflection.specimens.uses_local"; + + let ok = match case { + "outer_variant_scaffold" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { + return Ok(Value::Bool(false)); + }; + value_contains_atom_identity(&skeleton, ctx, "Scaffold") + && count_value_atom_identity(&skeleton, ctx, "Scaffold") == 1 + } + "outer_variant_terminal" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_TERMINAL)? else { + return Ok(Value::Bool(false)); + }; + value_contains_atom_identity(&skeleton, ctx, "Terminal") + } + "declaration_ref_bind_fields" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { + return Ok(Value::Bool(false)); + }; + value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") + && value_contains_atom_identity( + &skeleton, + ctx, + "test.fixture.decl_facts_reflection.specimens", + ) + && value_contains_atom_identity(&skeleton, ctx, "planted_scaffold_specimen") + && !value_contains_atom_identity(&skeleton, ctx, "WholeDeclaration") + } + "named_field_declaration_ref" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_NAMED_FIELD_REF)? else { + return Ok(Value::Bool(false)); + }; + value_contains_atom_identity(&skeleton, ctx, "NamedField") + && value_contains_atom_identity(&skeleton, ctx, "dissolves_to") + } + "plain_int_no_variant_atoms" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_PLAIN_INT)? else { + return Ok(Value::Bool(false)); + }; + let mut atoms = std::collections::BTreeSet::new(); + collect_value_atom_identities(&skeleton, ctx, &mut atoms); + !atoms.contains("Scaffold") + && !atoms.contains("Terminal") + && !atoms.contains("SingleAuthority") + && !atoms.contains("DeclarationRef") + } + "nullary_variant_value_absent_top_level" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_NULLARY)? else { + return Ok(Value::Bool(false)); + }; + !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") + } + "nullary_variant_value_absent_nested" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { + return Ok(Value::Bool(false)); + }; + !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") + } + "fn_body_param_no_variant_atoms" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_USES_PARAM_ONLY)? else { + return Ok(Value::Bool(false)); + }; + !value_contains_atom_identity(&skeleton, ctx, "Scaffold") + && !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") + && !value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") + } + "fn_body_local_no_variant_atoms" => { + let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_USES_LOCAL)? else { + return Ok(Value::Bool(false)); + }; + !value_contains_atom_identity(&skeleton, ctx, "Scaffold") + && !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") + && !value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") + } + other => { + return Err(InterpError::TypeError { + msg: format!( + "decl_facts_reflection_fixture_witness: unknown case {other:?}" + ), + }); + } + }; + Ok(Value::Bool(ok)) +} diff --git a/src/v1/stage0/src/v1_compiler_infer_method.rs b/src/v1/stage0/src/v1_compiler_infer_method.rs index 9fddd6e4591..a0d0201bcbc 100644 --- a/src/v1/stage0/src/v1_compiler_infer_method.rs +++ b/src/v1/stage0/src/v1_compiler_infer_method.rs @@ -571,6 +571,11 @@ pub fn builtin_function_registry() -> Rc>> { "decl_facts".to_string(), list_of_type_variable("decl_fact_elem".to_string()), ); + let m = v1_rt::rc_map_insert( + m.clone(), + "decl_facts_reflection_fixture_witness_host".to_string(), + bool_type(), + ); let m = v1_rt::rc_map_insert( m.clone(), "export_signature_facts".to_string(), diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 47117dcb5e1..4c481faa8b0 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -10919,6 +10919,14 @@ macro_rules! v1_builtin_arms { )?)) }, + arm "free_call.decl_facts_reflection_fixture_witness_host" { "decl_facts_reflection_fixture_witness_host" } => { + let case = expect_str($positional.first().copied(), "decl_facts_reflection_fixture_witness_host")?; + Ok(Some(crate::coproduct_reflection::eval_decl_facts_reflection_fixture_witness( + $ctx, + &case, + )?)) + }, + arm "free_call.module_declaration_facts" { "module_declaration_facts" } => { let pool_roots = expect_str_list($positional.first().copied(), "module_declaration_facts")?; diff --git a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs index f58b6cc62e2..a9e0fbd862a 100644 --- a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs +++ b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs @@ -65,6 +65,7 @@ pub enum EvalBuiltinArm { FreeCallConceptDeclFacts, FreeCallExportSignatureFacts, FreeCallDeclFacts, + FreeCallDeclFactsReflectionFixtureWitnessHost, FreeCallModuleDeclarationFacts, FreeCallFactCardinalityDeclFacts, FreeCallLanguagesConsumerCensusDataDeclCount, @@ -193,6 +194,7 @@ pub fn lookup_eval_builtin_inner(spelling: &str) -> Option { "concept_decl_facts" => Some(EvalBuiltinArm::FreeCallConceptDeclFacts), "export_signature_facts" => Some(EvalBuiltinArm::FreeCallExportSignatureFacts), "decl_facts" => Some(EvalBuiltinArm::FreeCallDeclFacts), + "decl_facts_reflection_fixture_witness_host" => Some(EvalBuiltinArm::FreeCallDeclFactsReflectionFixtureWitnessHost), "module_declaration_facts" => Some(EvalBuiltinArm::FreeCallModuleDeclarationFacts), "fact_cardinality_decl_facts" => Some(EvalBuiltinArm::FreeCallFactCardinalityDeclFacts), "languages_consumer_census_data_decl_count" => Some(EvalBuiltinArm::FreeCallLanguagesConsumerCensusDataDeclCount), @@ -319,6 +321,7 @@ macro_rules! eval_builtin_inner_arm { ("free_call.concept_decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallConceptDeclFacts }; ("free_call.export_signature_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallExportSignatureFacts }; ("free_call.decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallDeclFacts }; + ("free_call.decl_facts_reflection_fixture_witness_host") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallDeclFactsReflectionFixtureWitnessHost }; ("free_call.module_declaration_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallModuleDeclarationFacts }; ("free_call.fact_cardinality_decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallFactCardinalityDeclFacts }; ("free_call.languages_consumer_census_data_decl_count") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallLanguagesConsumerCensusDataDeclCount }; diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag index 334c23a389d..13ddad6eb9c 100644 --- a/src/v2/std/decl_facts_skeleton.dag +++ b/src/v2/std/decl_facts_skeleton.dag @@ -1,99 +1,9 @@ module v2.std.decl_facts_skeleton -import std.types { List, String } -import v2.std.algebra { Empty, list_append, list_snoc_item, length, count_where } -import v2.std.collection { Absent, Present, list_at_optional } -import v2.std.compilers.lexing { symbol_lexeme } -import v2.std.decl_index { DeclFact, decl_facts } -import v2.std.node { Node, TypeNode, Atom, Conj } -import v2.std.node_query { node_positional_child_targets } +import std.types { String, Bool } -data decl_facts_nullary_variant_value_identity_gap_note: String = "PARSE-ONLY decl_facts marshal surfaces outer record-literal variant constructors structurally (ExprRecordLit authored name on the AST node — Scaffold, Terminal, DeclarationRef, WholeDeclaration, NamedField, etc.). Nullary coproduct variant VALUE references (ExprVar such as dissolves_to: SingleAuthority) require infer-stamped VarBindingKind::VariantValueBinding from lookup_variant_parent_enum(InferScope, name), which needs a populated type_env and locals — v1.compiler.infer infer_var_binding_kind. The parse-only decl_facts_corpus_walk path never runs inference, so binding_kind stays None on every ExprVar and the marshaller cannot ground variant-value identity without a naming convention or arity heuristic. Dissolve-on: decl_facts data-init or fn-body marshal stamps VariantValueBinding via per-file import/type resolution before marshal_generic, or the corpus walk runs the infer binding-kind pass on each module before skeleton emission." +data decl_facts_nullary_variant_value_identity_gap_note: String = "PARSE-ONLY decl_facts marshal surfaces outer record-literal variant constructors structurally (ExprRecordLit authored name on the AST node — Scaffold, Terminal, DeclarationRef, NamedField, etc.). Nullary coproduct variant VALUE references (ExprVar such as dissolves_to: SingleAuthority or field: WholeDeclaration) require infer-stamped VarBindingKind::VariantValueBinding from lookup_variant_parent_enum(InferScope, name), which needs a populated type_env and locals — v1.compiler.infer infer_var_binding_kind. The parse-only decl_facts_corpus_walk path never runs inference, so binding_kind stays None on every ExprVar and the marshaller cannot ground variant-value identity without a naming convention or arity heuristic. Dissolve-on: decl_facts data-init or fn-body marshal stamps VariantValueBinding via per-file import/type resolution before marshal_generic, or the corpus walk runs the infer binding-kind pass on each module before skeleton emission." -data decl_facts_reflection_fixture_pool_roots: List = [ - "dag/test/fixture/decl_facts_reflection", -] - -fn decl_facts_skeleton_atom_lexeme_optional(node: Node) -> String? { - match node.kind { - TypeNode { connective: Atom { identity: sym } } => - Present { value: symbol_lexeme(sym: sym) } - TypeNode { connective: Conj } => - fold( - node_positional_child_targets(node: node), - init: Absent, - f: fn(acc, nested) { - match acc { - Present { value: _ } => acc - Absent => decl_facts_skeleton_atom_lexeme_optional(node: nested) - } - } - ) - _ => Absent - } -} - -fn node_skeleton_edge_targets(node: Node) -> List { - fold(node.children, init: Empty, f: fn(acc, e) { - list_snoc_item(xs: acc, item: e.target) - }) -} - -fn node_skeleton_atom_lexemes(node: Node) -> List { - let here = match node.kind { - TypeNode { connective: Atom { identity: sym } } => [symbol_lexeme(sym: sym)] - _ => [] - } - fold( - node_skeleton_edge_targets(node: node), - init: here, - f: fn(acc, child) { - list_append(left: acc, right: node_skeleton_atom_lexemes(node: child)) - } - ) -} - -fn skeleton_contains_atom_lexeme(node: Node, lexeme: String) -> Bool { - count_where( - xs: node_skeleton_atom_lexemes(node: node), - predicate: fn(lex) { lex == lexeme } - ) > 0 -} - -fn skeleton_atom_lexeme_count(node: Node, lexeme: String) -> Int { - count_where( - xs: node_skeleton_atom_lexemes(node: node), - predicate: fn(lex) { lex == lexeme } - ) -} - -fn decl_fact_for_qualified_name(facts: List, qn: String) -> DeclFact? { - fold( - facts, - init: Absent, - f: fn(acc, fact) { - match acc { - Present { value: _ } => acc - Absent => - match fact.qualified_name == qn { - true => Present { value: fact } - false => Absent - } - } - } - ) -} - -fn decl_fact_skeleton_for_qualified_name( - facts: List, - qn: String -) -> Node? { - match decl_fact_for_qualified_name(facts: facts, qn: qn) { - Present { value: fact } => Present { value: fact.node } - Absent => Absent - } -} - -fn decl_facts_reflection_fixture_facts() -> List { - decl_facts(pool_roots: decl_facts_reflection_fixture_pool_roots) +fn decl_facts_reflection_fixture_witness(case: String) -> Bool { + decl_facts_reflection_fixture_witness_host(case: case) } From 9bec4c6bf4a9140e4faf710365284d054daeee75 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 19:40:13 +0000 Subject: [PATCH 5/7] Honest bounded constructor-lexeme reflection for decl_facts prerequisite. Rename reflected output to OuterRecordConstructorLexeme, move fixture loaders into test witness_support, add total DeclFactQualifiedNameResolution, remove the unconsumed VariantValueBinding marshal arm from this slice, and restore executing .dag witnesses (12/12 pass) without host-builtin recursion. Co-authored-by: Cursor --- .../v1_interpreter_primitive_surface.dag | 8 - dag/std/primitives.dag | 1 - .../decl_facts_reflection_witness_test.dag | 142 ++++++++-- .../decl_facts_reflection/witness_support.dag | 28 ++ src/v1/04_method.dag | 1 - src/v1/stage0/src/coproduct_reflection.rs | 257 +----------------- src/v1/stage0/src/v1_compiler_infer_method.rs | 5 - src/v1/stage0/src/v1_interpreter.rs | 8 - .../src/v1_interpreter_dispatch_generated.rs | 3 - .../decl_facts_skeleton_reflection_test.rs | 13 +- src/v2/std/decl_facts_skeleton.dag | 138 +++++++++- 11 files changed, 299 insertions(+), 305 deletions(-) create mode 100644 dag/test/fixture/decl_facts_reflection/witness_support.dag diff --git a/dag/gunbc/v1_interpreter_primitive_surface.dag b/dag/gunbc/v1_interpreter_primitive_surface.dag index d1e25b726a0..c50af7b3642 100644 --- a/dag/gunbc/v1_interpreter_primitive_surface.dag +++ b/dag/gunbc/v1_interpreter_primitive_surface.dag @@ -712,14 +712,6 @@ fn v1_interpreter_authored_roster_arms() -> List List { "module_declaration_facts", "concept_decl_facts", "decl_facts", - "decl_facts_reflection_fixture_witness_host", "export_signature_facts", "fact_cardinality_decl_facts", "languages_consumer_census_data_decl_count", diff --git a/dag/test/claim/decl_facts_reflection_witness_test.dag b/dag/test/claim/decl_facts_reflection_witness_test.dag index 3d8cf17e174..a2a8d4c0677 100644 --- a/dag/test/claim/decl_facts_reflection_witness_test.dag +++ b/dag/test/claim/decl_facts_reflection_witness_test.dag @@ -2,45 +2,145 @@ module test.claim.decl_facts_reflection_witness_test import v2.std.decl_facts_skeleton { decl_facts_nullary_variant_value_identity_gap_note, - decl_facts_reflection_fixture_witness, + decl_facts_outer_record_constructor_lexeme_authority_note, + skeleton_contains_marshal_atom_lexeme, + skeleton_contains_outer_record_constructor_lexeme, + skeleton_marshal_atom_lexeme_count, + skeleton_outer_record_constructor_lexeme_count, } +import test.fixture.decl_facts_reflection.witness_support { + decl_facts_reflection_fixture_facts, + decl_fact_skeleton_for_qualified_name, +} +import v2.std.collection { Absent, Present } +import v2.std.algebra { length } + +data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796), PARTIAL: outer record-constructor SPELLING (OuterRecordConstructorLexeme) is reflected and tested via executing witnesses. Exact parent-variant identity and nullary variant VALUE identity remain open — parse-only decl_facts cannot ground dissolves_to: SingleAuthority or field: WholeDeclaration; see decl_facts_nullary_variant_value_identity_gap_note. Rust tests in v1-compiler-tests are developer convenience only (compile-only in CI; nextest retired 2026-07-11)." -data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796): executing evidence for narrow decl_facts skeleton reflection. Positive controls prove outer record-literal variant constructors and DeclarationRef field literals surface structurally via eval_decl_facts Value oracle (host bridge — substrate Node walk cannot read marshaled atoms). Gap controls prove nullary variant VALUE references (ExprVar SingleAuthority, WholeDeclaration) are absent under parse-only marshal. Rust tests in v1-compiler-tests are developer convenience only (compile-only in CI; nextest retired 2026-07-11)." +data qn_planted_scaffold_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen" +data qn_planted_terminal_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen" +data qn_planted_nullary_disposition_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen" +data qn_planted_named_field_ref_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen" +data qn_planted_plain_int_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen" +data qn_uses_param_only: String = "test.fixture.decl_facts_reflection.specimens.uses_param_only" +data qn_uses_local: String = "test.fixture.decl_facts_reflection.specimens.uses_local" -test fn w_outer_variant_scaffold_constructor_surfaces() -> Bool { - decl_facts_reflection_fixture_witness(case: "outer_variant_scaffold") +test fn w_fixture_pool_decl_facts_nonempty() -> Bool { + length(xs: decl_facts_reflection_fixture_facts()) > 0 } -test fn w_outer_variant_terminal_constructor_surfaces() -> Bool { - decl_facts_reflection_fixture_witness(case: "outer_variant_terminal") +test fn w_outer_record_constructor_scaffold_lexeme_surfaces() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && skeleton_outer_record_constructor_lexeme_count(node: skeleton, lexeme: "Scaffold") == 1 + Absent => false + } +} + +test fn w_outer_record_constructor_terminal_lexeme_surfaces() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_terminal_specimen + ) { + Present { value: skeleton } => + skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Terminal") + Absent => false + } } test fn w_declaration_ref_bind_fields_surface_in_scaffold_specimen() -> Bool { - decl_facts_reflection_fixture_witness(case: "declaration_ref_bind_fields") + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + && skeleton_contains_marshal_atom_lexeme( + node: skeleton, + lexeme: "test.fixture.decl_facts_reflection.specimens" + ) + && skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "planted_scaffold_specimen") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "WholeDeclaration") + Absent => false + } } test fn w_named_field_declaration_ref_surfaces() -> Bool { - decl_facts_reflection_fixture_witness(case: "named_field_declaration_ref") + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_named_field_ref_specimen + ) { + Present { value: skeleton } => + skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "NamedField") + && skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "dissolves_to") + Absent => false + } } -test fn w_plain_int_specimen_has_no_coproduct_variant_atoms() -> Bool { - decl_facts_reflection_fixture_witness(case: "plain_int_no_variant_atoms") +test fn w_plain_int_specimen_has_no_coproduct_constructor_lexemes() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_plain_int_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Terminal") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } } test fn w_nullary_variant_value_absent_without_infer_stamping_top_level() -> Bool { - decl_facts_reflection_fixture_witness(case: "nullary_variant_value_absent_top_level") + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_nullary_disposition_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + Absent => false + } } test fn w_nullary_variant_value_absent_without_infer_stamping_nested() -> Bool { - decl_facts_reflection_fixture_witness(case: "nullary_variant_value_absent_nested") + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_scaffold_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + Absent => false + } } -test fn w_fn_body_param_reference_does_not_gain_variant_atoms() -> Bool { - decl_facts_reflection_fixture_witness(case: "fn_body_param_no_variant_atoms") +test fn w_fn_body_param_reference_does_not_gain_constructor_lexemes() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_param_only + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } } -test fn w_fn_body_local_bindings_do_not_gain_variant_atoms() -> Bool { - decl_facts_reflection_fixture_witness(case: "fn_body_local_no_variant_atoms") +test fn w_fn_body_local_bindings_do_not_gain_constructor_lexemes() -> Bool { + match decl_fact_skeleton_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_local + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } } test fn w_gap_note_names_infer_binding_kind_authority() -> Bool { @@ -52,3 +152,13 @@ test fn w_gap_note_names_infer_binding_kind_authority() -> Bool { pattern: "lookup_variant_parent_enum", ) } + +test fn w_authority_note_names_constructor_lexeme_not_variant_identity() -> Bool { + string_contains( + s: decl_facts_outer_record_constructor_lexeme_authority_note, + pattern: "OuterRecordConstructorLexeme", + ) && string_contains( + s: decl_facts_outer_record_constructor_lexeme_authority_note, + pattern: "not variant identity", + ) +} diff --git a/dag/test/fixture/decl_facts_reflection/witness_support.dag b/dag/test/fixture/decl_facts_reflection/witness_support.dag new file mode 100644 index 00000000000..69d4113e54a --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/witness_support.dag @@ -0,0 +1,28 @@ +module test.fixture.decl_facts_reflection.witness_support + +import std.types { List } +import v2.std.decl_facts_skeleton { + DeclFactQualifiedNameResolution, + decl_fact_skeleton_for_resolution, + resolve_decl_fact_by_qualified_name, +} +import v2.std.decl_index { DeclFact, decl_facts } +import v2.std.node { Node } +import v2.std.collection { Absent, Present } + +data decl_facts_reflection_fixture_pool_roots: List = [ + "dag/test/fixture/decl_facts_reflection", +] + +fn decl_facts_reflection_fixture_facts() -> List { + decl_facts(pool_roots: decl_facts_reflection_fixture_pool_roots) +} + +fn decl_fact_skeleton_for_qualified_name( + facts: List, + qn: String +) -> Node? { + decl_fact_skeleton_for_resolution( + resolution: resolve_decl_fact_by_qualified_name(facts: facts, qn: qn) + ) +} diff --git a/src/v1/04_method.dag b/src/v1/04_method.dag index 5fc21360a11..7d2361ca535 100644 --- a/src/v1/04_method.dag +++ b/src/v1/04_method.dag @@ -178,7 +178,6 @@ fn builtin_function_registry() -> Map { let m = map_insert(m, "module_declaration_facts", list_of_type_variable(id: "module_declaration_fact_elem")) let m = map_insert(m, "concept_decl_facts", list_of_type_variable(id: "concept_decl_elem")) let m = map_insert(m, "decl_facts", list_of_type_variable(id: "decl_fact_elem")) - let m = map_insert(m, "decl_facts_reflection_fixture_witness_host", bool_type) let m = map_insert(m, "export_signature_facts", list_of_type_variable(id: "decl_fact_elem")) let m = map_insert(m, "fact_cardinality_decl_facts", list_of_type_variable(id: "fact_cardinality_decl_fact_elem")) let m = map_insert(m, "languages_consumer_census_data_decl_count", int_type) diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index b62c518a265..a35464c33a1 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -655,28 +655,6 @@ fn hoist_call_arg_string_literal_edges( } } -/// Nullary coproduct variant VALUE atom in declaration skeletons. -/// -/// Only when inference has stamped `VariantValueBinding` on the `ExprVar` (via -/// `lookup_variant_parent_enum`). Parse-only `decl_facts` marshal leaves -/// `binding_kind: None` on every `ExprVar`, so nullary variant value identity is -/// intentionally unreachable there — see `v2.std.decl_facts_skeleton` -/// `decl_facts_nullary_variant_value_identity_gap_note`. -fn should_emit_nullary_variant_value_atom( - node: &Rc, - name: &str, - param_names: &[String], - binding_kind: Option<&Rc>, -) -> bool { - if name.is_empty() || node_references_param(node, name, param_names) { - return false; - } - matches!( - binding_kind, - Some(bk) if matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }) - ) -} - fn marshal_generic( ctx: &InterpContext, node: &Rc, @@ -687,7 +665,9 @@ fn marshal_generic( let mut edges: Vec = Vec::with_capacity(node.children.len() + 1); let mut refs: std::collections::BTreeSet = std::collections::BTreeSet::new(); if matches!(node.expr_data.as_ref(), ExprData::ExprRecordLit { .. }) && !name.is_empty() { - // Outer coproduct/record variant constructor (e.g. Scaffold, Terminal, DeclarationRef). + // Outer record-constructor SPELLING (OuterRecordConstructorLexeme) from ExprRecordLit + // authored name — not resolved parent-variant identity; see + // `v2.std.decl_facts_skeleton` `decl_facts_outer_record_constructor_lexeme_authority_note`. edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); } else if node_references_param(node, &name, param_names) { edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); @@ -695,17 +675,6 @@ fn marshal_generic( // G2 live-read call reachability: callee atoms make cross-fn carrier chains // visible in the fn-arrow skeleton (docs/plans/live-read-witness-classification-design.md P1). edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); - } else if !name.is_empty() { - if let ExprData::ExprVar { binding_kind } = node.expr_data.as_ref() { - if should_emit_nullary_variant_value_atom( - node, - &name, - param_names, - binding_kind.as_ref(), - ) { - edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); - } - } } if let Some(literal_edge) = marshal_string_literal_atom(ctx, node) { edges.push(literal_edge); @@ -1491,223 +1460,3 @@ fn outcome_rejected_value(ctx: &InterpContext, reason: &str) -> Value { )]), } } - -const DECL_FACTS_REFLECTION_FIXTURE_POOL: &str = "dag/test/fixture/decl_facts_reflection"; - -fn decl_facts_reflection_fixture_roots() -> Vec { - vec![DECL_FACTS_REFLECTION_FIXTURE_POOL.to_string()] -} - -fn collect_value_atom_identities( - val: &Value, - ctx: &InterpContext, - out: &mut std::collections::BTreeSet, -) { - match val { - Value::Variant { - variant_name, - fields, - .. - } => { - if ctx.sym_eq(*variant_name, "Atom") { - if let Some((_, Value::Str(identity))) = - fields.iter().find(|(k, _)| ctx.sym_eq(*k, "identity")) - { - out.insert(identity.to_string()); - } - } - for (_, v) in fields.iter() { - collect_value_atom_identities(v, ctx, out); - } - } - Value::Record { fields, .. } => { - for (_, v) in fields.iter() { - collect_value_atom_identities(v, ctx, out); - } - } - Value::List(items) => { - for v in items.iter() { - collect_value_atom_identities(v, ctx, out); - } - } - _ => {} - } -} - -fn count_value_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> usize { - let mut count = 0usize; - fn walk(val: &Value, ctx: &InterpContext, identity: &str, count: &mut usize) { - match val { - Value::Variant { - variant_name, - fields, - .. - } => { - if ctx.sym_eq(*variant_name, "Atom") - && fields.iter().any(|(k, v)| { - ctx.sym_eq(*k, "identity") - && matches!(v, Value::Str(s) if s.as_str() == identity) - }) - { - *count += 1; - } - for (_, v) in fields.iter() { - walk(v, ctx, identity, count); - } - } - Value::Record { fields, .. } => { - for (_, v) in fields.iter() { - walk(v, ctx, identity, count); - } - } - Value::List(items) => { - for v in items.iter() { - walk(v, ctx, identity, count); - } - } - _ => {} - } - } - walk(val, ctx, identity, &mut count); - count -} - -fn value_contains_atom_identity(val: &Value, ctx: &InterpContext, identity: &str) -> bool { - count_value_atom_identity(val, ctx, identity) > 0 -} - -fn decl_fact_node_skeleton_value( - ctx: &InterpContext, - rows: &Value, - qualified_name: &str, -) -> Option { - let Value::List(items) = rows else { - return None; - }; - for row in items.iter() { - let Value::Record { fields, .. } = row else { - continue; - }; - let Some(Value::Str(qn)) = ctx.field(fields, "qualified_name") else { - continue; - }; - if qn != qualified_name { - continue; - } - return ctx.field(fields, "node").cloned(); - } - None -} - -fn decl_facts_reflection_skeleton_for( - ctx: &InterpContext, - qualified_name: &str, -) -> InterpResult> { - let rows = eval_decl_facts(ctx, &decl_facts_reflection_fixture_roots())?; - Ok(decl_fact_node_skeleton_value(ctx, &rows, qualified_name)) -} - -/// Host builtin backing `dag/test/claim/decl_facts_reflection_witness_test.dag`. -/// Executes marshaled-skeleton checks over the hermetic fixture pool via `eval_decl_facts` -/// (Value oracle — substrate `Node` pattern matching cannot read interpreter skeleton atoms). -pub fn eval_decl_facts_reflection_fixture_witness( - ctx: &InterpContext, - case: &str, -) -> InterpResult { - const QN_SCAFFOLD: &str = - "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen"; - const QN_TERMINAL: &str = - "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen"; - const QN_NULLARY: &str = - "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen"; - const QN_NAMED_FIELD_REF: &str = - "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen"; - const QN_PLAIN_INT: &str = - "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen"; - const QN_USES_PARAM_ONLY: &str = - "test.fixture.decl_facts_reflection.specimens.uses_param_only"; - const QN_USES_LOCAL: &str = "test.fixture.decl_facts_reflection.specimens.uses_local"; - - let ok = match case { - "outer_variant_scaffold" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { - return Ok(Value::Bool(false)); - }; - value_contains_atom_identity(&skeleton, ctx, "Scaffold") - && count_value_atom_identity(&skeleton, ctx, "Scaffold") == 1 - } - "outer_variant_terminal" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_TERMINAL)? else { - return Ok(Value::Bool(false)); - }; - value_contains_atom_identity(&skeleton, ctx, "Terminal") - } - "declaration_ref_bind_fields" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { - return Ok(Value::Bool(false)); - }; - value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") - && value_contains_atom_identity( - &skeleton, - ctx, - "test.fixture.decl_facts_reflection.specimens", - ) - && value_contains_atom_identity(&skeleton, ctx, "planted_scaffold_specimen") - && !value_contains_atom_identity(&skeleton, ctx, "WholeDeclaration") - } - "named_field_declaration_ref" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_NAMED_FIELD_REF)? else { - return Ok(Value::Bool(false)); - }; - value_contains_atom_identity(&skeleton, ctx, "NamedField") - && value_contains_atom_identity(&skeleton, ctx, "dissolves_to") - } - "plain_int_no_variant_atoms" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_PLAIN_INT)? else { - return Ok(Value::Bool(false)); - }; - let mut atoms = std::collections::BTreeSet::new(); - collect_value_atom_identities(&skeleton, ctx, &mut atoms); - !atoms.contains("Scaffold") - && !atoms.contains("Terminal") - && !atoms.contains("SingleAuthority") - && !atoms.contains("DeclarationRef") - } - "nullary_variant_value_absent_top_level" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_NULLARY)? else { - return Ok(Value::Bool(false)); - }; - !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") - } - "nullary_variant_value_absent_nested" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_SCAFFOLD)? else { - return Ok(Value::Bool(false)); - }; - !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") - } - "fn_body_param_no_variant_atoms" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_USES_PARAM_ONLY)? else { - return Ok(Value::Bool(false)); - }; - !value_contains_atom_identity(&skeleton, ctx, "Scaffold") - && !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") - && !value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") - } - "fn_body_local_no_variant_atoms" => { - let Some(skeleton) = decl_facts_reflection_skeleton_for(ctx, QN_USES_LOCAL)? else { - return Ok(Value::Bool(false)); - }; - !value_contains_atom_identity(&skeleton, ctx, "Scaffold") - && !value_contains_atom_identity(&skeleton, ctx, "SingleAuthority") - && !value_contains_atom_identity(&skeleton, ctx, "DeclarationRef") - } - other => { - return Err(InterpError::TypeError { - msg: format!( - "decl_facts_reflection_fixture_witness: unknown case {other:?}" - ), - }); - } - }; - Ok(Value::Bool(ok)) -} diff --git a/src/v1/stage0/src/v1_compiler_infer_method.rs b/src/v1/stage0/src/v1_compiler_infer_method.rs index a0d0201bcbc..9fddd6e4591 100644 --- a/src/v1/stage0/src/v1_compiler_infer_method.rs +++ b/src/v1/stage0/src/v1_compiler_infer_method.rs @@ -571,11 +571,6 @@ pub fn builtin_function_registry() -> Rc>> { "decl_facts".to_string(), list_of_type_variable("decl_fact_elem".to_string()), ); - let m = v1_rt::rc_map_insert( - m.clone(), - "decl_facts_reflection_fixture_witness_host".to_string(), - bool_type(), - ); let m = v1_rt::rc_map_insert( m.clone(), "export_signature_facts".to_string(), diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 4c481faa8b0..47117dcb5e1 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -10919,14 +10919,6 @@ macro_rules! v1_builtin_arms { )?)) }, - arm "free_call.decl_facts_reflection_fixture_witness_host" { "decl_facts_reflection_fixture_witness_host" } => { - let case = expect_str($positional.first().copied(), "decl_facts_reflection_fixture_witness_host")?; - Ok(Some(crate::coproduct_reflection::eval_decl_facts_reflection_fixture_witness( - $ctx, - &case, - )?)) - }, - arm "free_call.module_declaration_facts" { "module_declaration_facts" } => { let pool_roots = expect_str_list($positional.first().copied(), "module_declaration_facts")?; diff --git a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs index a9e0fbd862a..f58b6cc62e2 100644 --- a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs +++ b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs @@ -65,7 +65,6 @@ pub enum EvalBuiltinArm { FreeCallConceptDeclFacts, FreeCallExportSignatureFacts, FreeCallDeclFacts, - FreeCallDeclFactsReflectionFixtureWitnessHost, FreeCallModuleDeclarationFacts, FreeCallFactCardinalityDeclFacts, FreeCallLanguagesConsumerCensusDataDeclCount, @@ -194,7 +193,6 @@ pub fn lookup_eval_builtin_inner(spelling: &str) -> Option { "concept_decl_facts" => Some(EvalBuiltinArm::FreeCallConceptDeclFacts), "export_signature_facts" => Some(EvalBuiltinArm::FreeCallExportSignatureFacts), "decl_facts" => Some(EvalBuiltinArm::FreeCallDeclFacts), - "decl_facts_reflection_fixture_witness_host" => Some(EvalBuiltinArm::FreeCallDeclFactsReflectionFixtureWitnessHost), "module_declaration_facts" => Some(EvalBuiltinArm::FreeCallModuleDeclarationFacts), "fact_cardinality_decl_facts" => Some(EvalBuiltinArm::FreeCallFactCardinalityDeclFacts), "languages_consumer_census_data_decl_count" => Some(EvalBuiltinArm::FreeCallLanguagesConsumerCensusDataDeclCount), @@ -321,7 +319,6 @@ macro_rules! eval_builtin_inner_arm { ("free_call.concept_decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallConceptDeclFacts }; ("free_call.export_signature_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallExportSignatureFacts }; ("free_call.decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallDeclFacts }; - ("free_call.decl_facts_reflection_fixture_witness_host") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallDeclFactsReflectionFixtureWitnessHost }; ("free_call.module_declaration_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallModuleDeclarationFacts }; ("free_call.fact_cardinality_decl_facts") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallFactCardinalityDeclFacts }; ("free_call.languages_consumer_census_data_decl_count") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallLanguagesConsumerCensusDataDeclCount }; diff --git a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs index 7e31d40edc7..5896ea0336c 100644 --- a/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs +++ b/src/v1/tests/src/decl_facts_skeleton_reflection_test.rs @@ -1,5 +1,8 @@ //! Generic `decl_facts` data-init skeleton reflection prerequisite (step 1 / #7759 split). //! +//! PARTIAL capability: outer record-constructor SPELLING (OuterRecordConstructorLexeme) only. +//! Exact parent-variant identity and nullary variant VALUE identity remain open. +//! //! Developer convenience only — CI runs `cargo check` on this crate, not `cargo test` //! (nextest retired 2026-07-11). Merge-blocking evidence lives in //! `dag/test/claim/decl_facts_reflection_witness_test.dag`. @@ -148,29 +151,29 @@ fn fixture_decl_facts(ctx: &InterpContext) -> Value { } #[test] -fn outer_variant_identity_scaffold_specimen() { +fn outer_record_constructor_lexeme_scaffold_specimen() { let ctx = wet_ctx(); let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_SCAFFOLD) .expect("missing scaffold specimen"); assert!( value_contains_atom_identity(&skeleton, &ctx, "Scaffold"), - "Scaffold outer variant constructor must appear in skeleton" + "Scaffold outer record-constructor lexeme must appear in skeleton" ); assert_eq!( count_atom_identity(&skeleton, &ctx, "Scaffold"), 1, - "Scaffold constructor atom must appear exactly once (no duplicate emission)" + "Scaffold constructor lexeme must appear exactly once (no duplicate emission)" ); } #[test] -fn outer_variant_identity_terminal_specimen() { +fn outer_record_constructor_lexeme_terminal_specimen() { let ctx = wet_ctx(); let skeleton = decl_fact_node_skeleton(&ctx, &fixture_decl_facts(&ctx), QN_TERMINAL) .expect("missing terminal specimen"); assert!( value_contains_atom_identity(&skeleton, &ctx, "Terminal"), - "Terminal outer variant constructor must appear in skeleton" + "Terminal outer record-constructor lexeme must appear in skeleton" ); } diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag index 13ddad6eb9c..f11f522c496 100644 --- a/src/v2/std/decl_facts_skeleton.dag +++ b/src/v2/std/decl_facts_skeleton.dag @@ -1,9 +1,139 @@ module v2.std.decl_facts_skeleton -import std.types { String, Bool } +import std.types { List, String } +import v2.std.algebra { + Empty, + HeadAbsent, + HeadFound, + count_where, + length, + list_head, + list_snoc_item, +} +import v2.std.collection { Absent, Present } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.decl_index { DeclFact } +import v2.std.node { Node, TypeNode, Atom, Conj } +import v2.std.node_query { node_positional_child_targets } + +type OuterRecordConstructorLexeme = String + +type SkeletonMarshalAtomLexeme = String + +data decl_facts_nullary_variant_value_identity_gap_note: String = "BOUNDED constructor-lexeme reflection (parse-only decl_facts): outer ExprRecordLit authored names surface as SkeletonMarshalAtomLexeme / OuterRecordConstructorLexeme atoms (Scaffold, Terminal, DeclarationRef, NamedField, etc.) — constructor SPELLING only, not parent coproduct or declaration identity. Nullary coproduct variant VALUE references (ExprVar such as dissolves_to: SingleAuthority or field: WholeDeclaration) require infer-stamped VarBindingKind::VariantValueBinding from lookup_variant_parent_enum(InferScope, name), which needs a populated type_env and locals — v1.compiler.infer infer_var_binding_kind. The parse-only decl_facts_corpus_walk path never runs inference, so binding_kind stays None on every ExprVar; exact parent-variant identity and nullary value identity remain open. Dissolve-on: decl_facts marshal stamps VariantValueBinding via per-file import/type resolution before marshal_generic, or the corpus walk runs the infer binding-kind pass on each module before skeleton emission." + +data decl_facts_outer_record_constructor_lexeme_authority_note: String = "OuterRecordConstructorLexeme is the honest name for ExprRecordLit-authored constructor spelling reflected in decl_facts skeleton atoms. It is not variant identity: parent coproduct, module, declaration, and variant resolution are separate open obligations. skeleton_contains_outer_record_constructor_lexeme reads the same marshal atom spine as skeleton_contains_marshal_atom_lexeme but types the query parameter honestly." + +type DeclFactQualifiedNameResolution + = DeclFactResolvedUnique { fact: DeclFact } + | DeclFactResolvedMissing { qualified_name: String } + | DeclFactResolvedAmbiguous { qualified_name: String, match_count: Int } + +fn decl_facts_matching_qualified_name(qn: String, facts: List) -> List { + fold(facts, init: Empty, f: fn(acc, fact) { + if fact.qualified_name == qn { + list_snoc_item(xs: acc, item: fact) + } else { + acc + } + }) +} + +fn resolve_decl_fact_by_qualified_name( + facts: List, + qn: String +) -> DeclFactQualifiedNameResolution { + let matches = decl_facts_matching_qualified_name(qn: qn, facts: facts) + let n = length(xs: matches) + if n == 0 { + DeclFactResolvedMissing { qualified_name: qn } + } else if n > 1 { + DeclFactResolvedAmbiguous { qualified_name: qn, match_count: n } + } else { + match list_head(xs: matches) { + HeadAbsent => DeclFactResolvedMissing { qualified_name: qn } + HeadFound { value: sole } => DeclFactResolvedUnique { fact: sole } + } + } +} + +fn skeleton_marshal_atom_lexeme_optional(node: Node) -> SkeletonMarshalAtomLexeme? { + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + Present { value: symbol_lexeme(sym: sym) } + TypeNode { connective: Conj } => + fold( + node_positional_child_targets(node: node), + init: Absent, + f: fn(acc, nested) { + match acc { + Present { value: _ } => acc + Absent => skeleton_marshal_atom_lexeme_optional(node: nested) + } + } + ) + _ => Absent + } +} -data decl_facts_nullary_variant_value_identity_gap_note: String = "PARSE-ONLY decl_facts marshal surfaces outer record-literal variant constructors structurally (ExprRecordLit authored name on the AST node — Scaffold, Terminal, DeclarationRef, NamedField, etc.). Nullary coproduct variant VALUE references (ExprVar such as dissolves_to: SingleAuthority or field: WholeDeclaration) require infer-stamped VarBindingKind::VariantValueBinding from lookup_variant_parent_enum(InferScope, name), which needs a populated type_env and locals — v1.compiler.infer infer_var_binding_kind. The parse-only decl_facts_corpus_walk path never runs inference, so binding_kind stays None on every ExprVar and the marshaller cannot ground variant-value identity without a naming convention or arity heuristic. Dissolve-on: decl_facts data-init or fn-body marshal stamps VariantValueBinding via per-file import/type resolution before marshal_generic, or the corpus walk runs the infer binding-kind pass on each module before skeleton emission." +fn node_skeleton_edge_targets(node: Node) -> List { + fold(node.children, init: Empty, f: fn(acc, e) { + list_snoc_item(xs: acc, item: e.target) + }) +} + +fn node_skeleton_marshal_atom_lexemes(node: Node) -> List { + let here = match node.kind { + TypeNode { connective: Atom { identity: sym } } => [symbol_lexeme(sym: sym)] + _ => [] + } + fold( + node_skeleton_edge_targets(node: node), + init: here, + f: fn(acc, child) { + fold( + node_skeleton_marshal_atom_lexemes(node: child), + init: acc, + f: fn(inner_acc, lex) { list_snoc_item(xs: inner_acc, item: lex) } + ) + } + ) +} + +fn skeleton_contains_marshal_atom_lexeme(node: Node, lexeme: SkeletonMarshalAtomLexeme) -> Bool { + count_where( + xs: node_skeleton_marshal_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) > 0 +} + +fn skeleton_marshal_atom_lexeme_count(node: Node, lexeme: SkeletonMarshalAtomLexeme) -> Int { + count_where( + xs: node_skeleton_marshal_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) +} + +fn skeleton_contains_outer_record_constructor_lexeme( + node: Node, + lexeme: OuterRecordConstructorLexeme +) -> Bool { + skeleton_contains_marshal_atom_lexeme(node: node, lexeme: lexeme) +} + +fn skeleton_outer_record_constructor_lexeme_count( + node: Node, + lexeme: OuterRecordConstructorLexeme +) -> Int { + skeleton_marshal_atom_lexeme_count(node: node, lexeme: lexeme) +} -fn decl_facts_reflection_fixture_witness(case: String) -> Bool { - decl_facts_reflection_fixture_witness_host(case: case) +fn decl_fact_skeleton_for_resolution( + resolution: DeclFactQualifiedNameResolution +) -> Node? { + match resolution { + DeclFactResolvedUnique { fact } => Present { value: fact.node } + DeclFactResolvedMissing { qualified_name: _ } => Absent + DeclFactResolvedAmbiguous { qualified_name: _, match_count: _ } => Absent + } } From adec0591db5ca6453fec3095fb47cf4a2fcebbb1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 19:43:37 +0000 Subject: [PATCH 6/7] WIP: Disposition owner parameter --- src/v2/std/decl_facts_skeleton.dag | 42 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag index f11f522c496..6df64541c20 100644 --- a/src/v2/std/decl_facts_skeleton.dag +++ b/src/v2/std/decl_facts_skeleton.dag @@ -6,9 +6,10 @@ import v2.std.algebra { HeadAbsent, HeadFound, count_where, + fold_list_right, length, list_head, - list_snoc_item, + list_reverse, } import v2.std.collection { Absent, Present } import v2.std.compilers.lexing { symbol_lexeme } @@ -30,13 +31,13 @@ type DeclFactQualifiedNameResolution | DeclFactResolvedAmbiguous { qualified_name: String, match_count: Int } fn decl_facts_matching_qualified_name(qn: String, facts: List) -> List { - fold(facts, init: Empty, f: fn(acc, fact) { + list_reverse(xs: fold(facts, init: Empty, f: fn(acc, fact) { if fact.qualified_name == qn { - list_snoc_item(xs: acc, item: fact) + Cons { head: fact, tail: acc } } else { acc } - }) + })) } fn resolve_decl_fact_by_qualified_name( @@ -77,27 +78,30 @@ fn skeleton_marshal_atom_lexeme_optional(node: Node) -> SkeletonMarshalAtomLexem } fn node_skeleton_edge_targets(node: Node) -> List { - fold(node.children, init: Empty, f: fn(acc, e) { - list_snoc_item(xs: acc, item: e.target) - }) + fold_list_right( + xs: node.children, + empty: Empty, + snoc: fn(acc, e) { Cons { head: e.target, tail: acc } } + ) } fn node_skeleton_marshal_atom_lexemes(node: Node) -> List { - let here = match node.kind { - TypeNode { connective: Atom { identity: sym } } => [symbol_lexeme(sym: sym)] - _ => [] - } - fold( - node_skeleton_edge_targets(node: node), - init: here, - f: fn(acc, child) { - fold( - node_skeleton_marshal_atom_lexemes(node: child), - init: acc, - f: fn(inner_acc, lex) { list_snoc_item(xs: inner_acc, item: lex) } + let child_lexemes = fold_list_right( + xs: node_skeleton_edge_targets(node: node), + empty: Empty, + snoc: fn(acc, child) { + fold_list_right( + xs: node_skeleton_marshal_atom_lexemes(node: child), + empty: acc, + snoc: fn(inner_acc, lex) { Cons { head: lex, tail: inner_acc } } ) } ) + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + Cons { head: symbol_lexeme(sym: sym), tail: child_lexemes } + _ => child_lexemes + } } fn skeleton_contains_marshal_atom_lexeme(node: Node, lexeme: SkeletonMarshalAtomLexeme) -> Bool { From 04904f49c542864fe88c09f5c1a671a1fafcddc0 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 4 Aug 2026 19:49:03 +0000 Subject: [PATCH 7/7] Fix O(n) list assembly in decl_facts_skeleton (.dag interpreted path). Replace list_snoc_item folds with Cons-prepend plus list_reverse once (matching facts) and fold_list_right with O(1) Cons snoc (lexeme flatten). The .dag FreeMonoid realization prices snoc as append, not refcount bump. Co-authored-by: Cursor --- src/v2/std/decl_facts_skeleton.dag | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag index 6df64541c20..c8842893ab0 100644 --- a/src/v2/std/decl_facts_skeleton.dag +++ b/src/v2/std/decl_facts_skeleton.dag @@ -78,11 +78,9 @@ fn skeleton_marshal_atom_lexeme_optional(node: Node) -> SkeletonMarshalAtomLexem } fn node_skeleton_edge_targets(node: Node) -> List { - fold_list_right( - xs: node.children, - empty: Empty, - snoc: fn(acc, e) { Cons { head: e.target, tail: acc } } - ) + list_reverse(xs: fold(node.children, init: Empty, f: fn(acc, e) { + Cons { head: e.target, tail: acc } + })) } fn node_skeleton_marshal_atom_lexemes(node: Node) -> List {