From a503e86788af3f617d0c52ef0a4529a3994a1822 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 4 Oct 2026 13:58:15 +0000 Subject: [PATCH 01/11] WIP: generic identity census, the derived-node-identity step-2 instrument (output grain) v1.tests.claim.generic_identity_census reads the typed graph and records, per stored type node, which generic-identity marks it carries. Its blind spot is a typed UNOBSERVED population in every receipt. Not ready to push: the mirror and lib.rs line were emitted by a local compiler that is not main's emitter and must be regenerated; the gunbc test label and the callee-argument column are not written yet. Co-Authored-By: Claude Opus 5.5 (1M context) --- fixtures/generic_identity_census/a.dag | 48 ++ src/v1/stage0/Cargo.toml | 6 + .../stage0/src/bin/generic_identity_census.rs | 73 +++ src/v1/stage0/src/lib.rs | 11 + .../v1_tests_claim_generic_identity_census.rs | 620 ++++++++++++++++++ .../tests/claim/generic_identity_census.dag | 354 ++++++++++ 6 files changed, 1112 insertions(+) create mode 100644 fixtures/generic_identity_census/a.dag create mode 100644 src/v1/stage0/src/bin/generic_identity_census.rs create mode 100644 src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs create mode 100644 src/v1/tests/claim/generic_identity_census.dag diff --git a/fixtures/generic_identity_census/a.dag b/fixtures/generic_identity_census/a.dag new file mode 100644 index 00000000000..30976852997 --- /dev/null +++ b/fixtures/generic_identity_census/a.dag @@ -0,0 +1,48 @@ +module gic.a + +type Box { + item: Q + rest: List +} + +fn head_of(xs: List, d: T) -> T { + match xs |> first { + Present { value: v } => v + Absent => d + } +} + +fn rename_of(xs: List, d: M) -> M { + match xs |> first { + Present { value: v } => v + Absent => d + } +} + +fn ints() -> List { + [1] +} + +fn take_s(s: String) -> String { + s +} + +fn caller_literal() -> Int { + head_of(xs: [1], d: 2) +} + +fn caller_function() -> Int { + head_of(xs: ints(), d: 2) +} + +fn caller_renamed() -> Int { + rename_of(xs: [1], d: 2) +} + +fn boxed() -> Box { + Box { item: 1, rest: [2] } +} + +fn unbox(b: Box) -> Q { + b.item +} diff --git a/src/v1/stage0/Cargo.toml b/src/v1/stage0/Cargo.toml index 5f6d36337f4..67cc2264359 100644 --- a/src/v1/stage0/Cargo.toml +++ b/src/v1/stage0/Cargo.toml @@ -92,6 +92,12 @@ path = "src/bin/claim_executor.rs" name = "carrier_realization_census" path = "src/bin/carrier_realization_census.rs" +# Transport for v1.tests.claim.generic_identity_census, the derived-node-identity step-2 instrument +# (docs/plans/derived-node-identity-design.md section 7). +[[bin]] +name = "generic_identity_census" +path = "src/bin/generic_identity_census.rs" + # XL-0T-CUTOVER census: over the complete production TypeOccurrence population, what the # module-wide bare-name TypeEnv path (X, as repaired by #9813) binds versus what the exact # per-occurrence containment authority (Y, diff --git a/src/v1/stage0/src/bin/generic_identity_census.rs b/src/v1/stage0/src/bin/generic_identity_census.rs new file mode 100644 index 00000000000..633354b4cad --- /dev/null +++ b/src/v1/stage0/src/bin/generic_identity_census.rs @@ -0,0 +1,73 @@ +#![allow(clippy::disallowed_macros)] +//! Transport for `v1.tests.claim.generic_identity_census`. +//! +//! The census is `.dag` and pure: subject sources go in as `SourceFile` DATA and the receipt comes +//! out as a `String`. This binary reads the subject files and writes the receipt, and decides +//! nothing about the measurement. It is the same split as `carrier_realization_census`, for the +//! same reason: `compile_to_resolved` over an authored source vector does not run under the +//! interpreter. +//! +//! Every failure arm refuses with a located message and a non-zero exit. A receipt with no +//! `UNOBSERVED` row is refused as well: the census states the populations it cannot reach in +//! every receipt, so their absence means the receipt is not the census's. + +use std::rc::Rc; + +use v1_compiler::v1_compiler_compile::SourceFile; +use v1_compiler::v1_tests_claim_generic_identity_census::generic_identity_census_from_sources; + +fn refuse(message: &str) -> ! { + eprintln!("generic-identity-census: REFUSED: {message}"); + std::process::exit(1); +} + +fn main() { + let args: Vec = std::env::args().skip(1).collect(); + if args.len() < 2 { + refuse("usage: generic_identity_census ..."); + } + let receipt_path = args[0].clone(); + let subject_paths = &args[1..]; + + let mut sources: Vec> = Vec::new(); + for path in subject_paths { + match std::fs::read_to_string(path) { + Ok(content) => sources.push(Rc::new(SourceFile { + path: path.clone(), + content, + })), + Err(err) => refuse(&format!("could not read subject source {path}: {err}")), + } + } + + let receipt = generic_identity_census_from_sources(Rc::new(sources.into())); + if receipt.starts_with("REFUSED") { + refuse(&format!("census refused: {receipt}")); + } + + let observed = receipt + .lines() + .filter(|line| line.starts_with("observed\t")) + .count(); + let unobserved = receipt + .lines() + .filter(|line| line.starts_with("UNOBSERVED\t")) + .count(); + if unobserved == 0 { + refuse("receipt carries no UNOBSERVED row; refusing a receipt that hides its blind spot"); + } + // No observed row is a real answer only for a subject with no generic and no kernel container, + // which no subject worth censusing is, so it refuses rather than reporting a zero that a + // failed walk would produce identically. + if observed == 0 { + refuse("census observed no row; refusing to write an empty receipt"); + } + + match std::fs::write(&receipt_path, &receipt) { + Ok(()) => println!( + "generic-identity-census: observed={observed} unobserved_populations={unobserved} subjects={} receipt={receipt_path}", + subject_paths.len() + ), + Err(err) => refuse(&format!("could not write receipt {receipt_path}: {err}")), + } +} diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index 03c0c4ec898..211bc6dfa27 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -697,6 +697,17 @@ pub mod v1_tests_claim_checkpoint_identity_keying_witness_test; suspicious_double_ref_op, clippy::all )] +pub mod v1_tests_claim_generic_identity_census; +#[allow( + unused_imports, + unused_variables, + unused_mut, + unused_parens, + dead_code, + non_shorthand_field_patterns, + suspicious_double_ref_op, + clippy::all +)] pub mod v1_tests_claim_item_kind_dissolves_shape_predicates_control_test; #[allow( unused_imports, diff --git a/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs b/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs new file mode 100644 index 00000000000..f9531ce694f --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs @@ -0,0 +1,620 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.generic_identity_census + +pub use crate::std_decl_ref::declaration_ref_display_key; +use crate::std_decl_ref::DeclField::{NamedField, TypeParameter, WholeDeclaration}; +pub use crate::std_decl_ref::{DeclField, DeclarationRef}; +pub use crate::std_types::{Bool, List, Map}; +pub use crate::v1_compiler_compile::compile_to_resolved; +pub use crate::v1_compiler_compile::{ResolvedPipelineResult, SourceFile}; +pub use crate::v1_compiler_infer::{param_is_generic_decl, type_node_label}; +pub use crate::v1_compiler_infer_items::{ResolvedGraph, TypedModule}; +use crate::v1_compiler_infer_sigs::ResolvedFormals::{ + DeclarationBoundFormals, KernelGroundedFormals, LocalFormalsAwaitingModuleContext, +}; +pub use crate::v1_compiler_infer_sigs::{ResolvedFormals, ResolvedFuncSig}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::v1_std_core::Connective::Arrow; +use crate::v1_std_core::InferredNode::{Resolved, TypeVariable}; +pub use crate::v1_std_core::{authored_name_at, param_node_name_at, param_node_type_expr}; +pub use crate::v1_std_core::{Connective, InferredNode, NewlineIndex, Node, ResolvedFormal}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GenericIdentityContext { + pub module_file: String, + pub enclosing: String, + pub generic_names: Rc>, + pub si: Rc>>, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GenericIdentityRow { + pub module_file: String, + pub enclosing: String, + pub carrier: String, + pub path: String, + pub parent_label: String, + pub spelled: String, + pub spelled_as_in_scope_generic: bool, + pub declaration: Option>, + pub type_variable_id: String, + pub carries_type_variable: bool, + pub resolved_label: String, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GenericIdentityUnobserved { + pub population: String, + pub reason: String, + pub would_observe: String, +} + +pub fn generic_identity_unobserved() -> Rc>> { + Rc::new(vec![Rc::new(GenericIdentityUnobserved { + population: "call-plan formals built by v1.compiler.infer build_call_application_plan and bound by infer_call_arguments_generic_pass".to_string(), + reason: "built and consumed inside one call's inference; the typed call node keeps the result type, not the plan".to_string(), + would_observe: "an output on the call plan carrier, or a claim calling build_call_application_plan over supplied formals".to_string(), +}), Rc::new(GenericIdentityUnobserved { + population: "the formal and actual nodes each unify_generics call compares, and the bindings of its substitution map".to_string(), + reason: "the map is local to the caller and is dropped after substitute_generics_apply returns".to_string(), + would_observe: "a claim calling unify_generics and substitute_generics_apply over supplied nodes".to_string(), +}), Rc::new(GenericIdentityUnobserved { + population: "intermediate nodes of alias_chain_target_after_args, expand_alias_chain_for_field_access and record_lit_instantiated_fields".to_string(), + reason: "only the final instantiated type is stored on the typed expression".to_string(), + would_observe: "a claim calling each constructor over supplied nodes".to_string(), +}), Rc::new(GenericIdentityUnobserved { + population: "signatures whose formals are LocalFormalsAwaitingModuleContext".to_string(), + reason: "the formals were never bound, so there is no ResolvedFormal to read; each such signature is one row with carrier formals_awaiting_module_context".to_string(), + would_observe: "the same reader after bind_local_func_conformance has run for that signature".to_string(), +})]) +} + +pub fn gi_is_bare(n: Rc) -> bool { + (((n.children.clone().len() as i64) == 0) && ((n.params.clone().len() as i64) == 0)) +} + +pub fn gi_declaration_is_type_parameter(d: Option>) -> bool { + match d.clone() { + Some(r) => match (*r.field.clone()).clone() { + DeclField::TypeParameter { name: _, .. } => true, + DeclField::WholeDeclaration => false, + DeclField::NamedField { field_name: _, .. } => false, + }, + std::option::Option::None => false, + } +} + +pub fn gi_type_variable_id(n: Rc) -> String { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::TypeVariable { id: tv, .. }) => tv.clone(), + _ => "".to_string(), + } +} + +pub fn gi_carries_type_variable(n: Rc) -> bool { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::TypeVariable { id: _, .. }) => true, + _ => false, + } +} + +pub fn gi_resolved_label(n: Rc, si: Rc>>) -> String { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => { + crate::v1_compiler_infer::type_node_label(rt.clone(), si.clone()) + } + _ => "".to_string(), + } +} + +pub fn gi_node_rows( + ctx: Rc, + carrier: String, + path: String, + parent_label: String, + n: Rc, +) -> Rc>> { + { + let spelled = crate::v1_compiler_infer::type_node_label(n.clone(), ctx.si.clone()); + let in_scope = { + let mut __found = false; + for g in ctx.generic_names.clone().iter().cloned() { + if (g.clone() == spelled.clone()) { + __found = true; + break; + } + } + __found + }; + let resolved = gi_resolved_label(n.clone(), ctx.si.clone()); + let has_tv = gi_carries_type_variable(n.clone()); + let renamed_in_argument_position = ((((parent_label.clone() != "".to_string()) + && (resolved.clone() != "".to_string())) + && (spelled.clone() != "".to_string())) + && (resolved.clone() != spelled.clone())); + if (((in_scope.clone() || has_tv.clone()) + || gi_declaration_is_type_parameter(n.declaration.clone())) + || renamed_in_argument_position.clone()) + { + Rc::new(vec![Rc::new(GenericIdentityRow { + module_file: ctx.module_file.clone(), + enclosing: ctx.enclosing.clone(), + carrier: carrier.clone(), + path: path.clone(), + parent_label: parent_label.clone(), + spelled: spelled.clone(), + spelled_as_in_scope_generic: in_scope.clone(), + declaration: n.declaration.clone(), + type_variable_id: gi_type_variable_id(n.clone()), + carries_type_variable: has_tv.clone(), + resolved_label: resolved.clone(), + })]) + } else { + Rc::new(vec![]) + } + } +} + +pub fn gi_type_rows( + ctx: Rc, + carrier: String, + path: String, + parent_label: String, + n: Rc, +) -> Rc>> { + stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { + if gi_is_bare(n.clone()) { + gi_node_rows( + ctx.clone(), + carrier.clone(), + path.clone(), + parent_label.clone(), + n.clone(), + ) + } else { + { + let own_label = + crate::v1_compiler_infer::type_node_label(n.clone(), ctx.si.clone()); + let is_arrow = match n.connective.clone() { + Connective::Arrow => true, + _ => false, + }; + let child_rows = Rc::new({ + let mut __result = Vec::new(); + for c in n.children.clone().iter().cloned() { + __result.extend( + (*gi_type_rows( + ctx.clone(), + carrier.clone(), + v1_rt::concat(path.clone(), "/arg".to_string()), + own_label.clone(), + c.clone(), + )) + .iter() + .cloned(), + ); + } + __result + }); + let param_rows = if is_arrow.clone() { + Rc::new({ + let mut __result = Vec::new(); + for p in n.params.clone().iter().cloned() { + __result.extend( + (*gi_type_rows( + ctx.clone(), + carrier.clone(), + v1_rt::concat(path.clone(), "/fn_param".to_string()), + own_label.clone(), + crate::v1_std_core::param_node_type_expr(p.clone()), + )) + .iter() + .cloned(), + ); + } + __result + }) + } else { + Rc::new(vec![]) + }; + let return_rows = if is_arrow.clone() { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => gi_type_rows( + ctx.clone(), + carrier.clone(), + v1_rt::concat(path.clone(), "/fn_return".to_string()), + own_label.clone(), + rt.clone(), + ), + _ => Rc::new(vec![]), + } + } else { + Rc::new(vec![]) + }; + v1_rt::concat( + v1_rt::concat(child_rows.clone(), param_rows.clone()), + return_rows.clone(), + ) + } + } + }) +} + +pub fn gi_formal_rows( + ctx: Rc, + f: Rc, +) -> Rc>> { + v1_rt::concat( + v1_rt::concat( + gi_type_rows( + ctx.clone(), + "formal_declared_type".to_string(), + f.parameter_identity.clone(), + "".to_string(), + f.declared_type.clone(), + ), + gi_type_rows( + ctx.clone(), + "formal_declaration_bound_conformance".to_string(), + f.parameter_identity.clone(), + "".to_string(), + f.declaration_bound_conformance.clone(), + ), + ), + gi_type_rows( + ctx.clone(), + "formal_substitution_basis".to_string(), + f.parameter_identity.clone(), + "".to_string(), + f.substitution_basis.clone(), + ), + ) +} + +pub fn gi_awaiting_row(ctx: Rc) -> Rc { + Rc::new(GenericIdentityRow { + module_file: ctx.module_file.clone(), + enclosing: ctx.enclosing.clone(), + carrier: "formals_awaiting_module_context".to_string(), + path: "".to_string(), + parent_label: "".to_string(), + spelled: "".to_string(), + spelled_as_in_scope_generic: false, + declaration: std::option::Option::None, + type_variable_id: "".to_string(), + carries_type_variable: false, + resolved_label: "".to_string(), + }) +} + +pub fn gi_formals_rows( + ctx: Rc, + formals: Rc, +) -> Rc>> { + match (*formals.clone()).clone() { + ResolvedFormals::DeclarationBoundFormals { formals: fs, .. } => Rc::new({ + let mut __result = Vec::new(); + for f in fs.iter().cloned() { + __result.extend((*gi_formal_rows(ctx.clone(), f.clone())).iter().cloned()); + } + __result + }), + ResolvedFormals::KernelGroundedFormals { formals: fs, .. } => Rc::new({ + let mut __result = Vec::new(); + for f in fs.iter().cloned() { + __result.extend((*gi_formal_rows(ctx.clone(), f.clone())).iter().cloned()); + } + __result + }), + ResolvedFormals::LocalFormalsAwaitingModuleContext => { + Rc::new(vec![gi_awaiting_row(ctx.clone())]) + } + } +} + +pub fn gi_generic_names( + params: Rc>>, + si: Rc>>, +) -> Rc> { + Rc::new({ + let mut __result = Vec::new(); + for p in Rc::new({ + let mut __result = Vec::new(); + for p in params.iter().cloned() { + if crate::v1_compiler_infer::param_is_generic_decl(p.clone(), si.clone()) { + __result.push(p); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(crate::v1_std_core::param_node_name_at( + p.clone(), + si.clone(), + )); + } + __result + }) +} + +pub fn gi_sig_rows( + module_file: String, + sig: Rc, + si: Rc>>, +) -> Rc>> { + { + let names = gi_generic_names(sig.params.clone(), si.clone()); + if ((names.clone().len() as i64) == 0) { + Rc::new(vec![]) + } else { + { + let ctx = Rc::new(GenericIdentityContext { + module_file: module_file.clone(), + enclosing: sig.name.clone(), + generic_names: names.clone(), + si: si.clone(), + }); + let value_params = Rc::new({ + let mut __result = Vec::new(); + for p in sig.params.clone().iter().cloned() { + if !crate::v1_compiler_infer::param_is_generic_decl(p.clone(), si.clone()) { + __result.push(p); + } + } + __result + }); + let param_rows = Rc::new({ + let mut __result = Vec::new(); + for p in value_params.iter().cloned() { + __result.extend( + (*gi_type_rows( + ctx.clone(), + "signature_param_type".to_string(), + crate::v1_std_core::param_node_name_at(p.clone(), si.clone()), + "".to_string(), + crate::v1_std_core::param_node_type_expr(p.clone()), + )) + .iter() + .cloned(), + ); + } + __result + }); + let return_rows = gi_type_rows( + ctx.clone(), + "signature_return".to_string(), + "".to_string(), + "".to_string(), + sig.inferred.clone(), + ); + v1_rt::concat( + v1_rt::concat(param_rows.clone(), return_rows.clone()), + gi_formals_rows(ctx.clone(), sig.resolved_formals.clone()), + ) + } + } + } +} + +pub fn gi_expr_rows( + ctx: Rc, + e: Rc, +) -> Rc>> { + stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { + let own = match e.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => gi_type_rows( + ctx.clone(), + "body_expression_type".to_string(), + crate::v1_compiler_infer::type_node_label(e.clone(), ctx.si.clone()), + "".to_string(), + rt.clone(), + ), + _ => Rc::new(vec![]), + }; + let child_rows = Rc::new({ + let mut __result = Vec::new(); + for c in e.children.clone().iter().cloned() { + __result.extend((*gi_expr_rows(ctx.clone(), c.clone())).iter().cloned()); + } + __result + }); + let param_rows = Rc::new({ + let mut __result = Vec::new(); + for p in e.params.clone().iter().cloned() { + __result.extend((*gi_expr_rows(ctx.clone(), p.clone())).iter().cloned()); + } + __result + }); + let body_rows = match e.body.clone() { + Some(b) => gi_expr_rows(ctx.clone(), b.clone()), + std::option::Option::None => Rc::new(vec![]), + }; + v1_rt::concat( + v1_rt::concat( + v1_rt::concat(own.clone(), child_rows.clone()), + param_rows.clone(), + ), + body_rows.clone(), + ) + }) +} + +pub fn gi_item_body_rows( + module_file: String, + item: Rc, + si: Rc>>, +) -> Rc>> { + match item.body.clone() { + std::option::Option::None => Rc::new(vec![]), + Some(b) => gi_expr_rows( + Rc::new(GenericIdentityContext { + module_file: module_file.clone(), + enclosing: crate::v1_std_core::authored_name_at(si.clone(), item.clone()), + generic_names: gi_generic_names(item.params.clone(), si.clone()), + si: si.clone(), + }), + b.clone(), + ), + } +} + +pub fn gi_module_rows( + m: Rc, + si: Rc>>, +) -> Rc>> { + { + let module_file = crate::v1_std_core::authored_name_at(si.clone(), m.module.clone()); + let sig_rows = Rc::new({ + let mut __result = Vec::new(); + for sig in Rc::new(v1_rt::map_values(&m.func_env.clone().local.clone())) + .iter() + .cloned() + { + __result.extend( + (*gi_sig_rows(module_file.clone(), sig.clone(), si.clone())) + .iter() + .cloned(), + ); + } + __result + }); + let body_rows = Rc::new({ + let mut __result = Vec::new(); + for item in m.items.clone().iter().cloned() { + __result.extend( + (*gi_item_body_rows(module_file.clone(), item.clone(), si.clone())) + .iter() + .cloned(), + ); + } + __result + }); + v1_rt::concat(sig_rows.clone(), body_rows.clone()) + } +} + +pub fn gi_bool_label(b: bool) -> String { + if b.clone() { + "yes".to_string() + } else { + "no".to_string() + } +} + +pub fn gi_declaration_label(d: Option>) -> String { + match d.clone() { + Some(r) => crate::std_decl_ref::declaration_ref_display_key(r.clone()), + std::option::Option::None => "none".to_string(), + } +} + +pub fn gi_mark_label(r: Rc) -> String { + { + let has_decl = gi_declaration_is_type_parameter(r.declaration.clone()); + if (has_decl.clone() && r.carries_type_variable.clone()) { + "declaration+type_variable".to_string() + } else { + if has_decl.clone() { + "declaration_only".to_string() + } else { + if r.carries_type_variable.clone() { + "type_variable_only".to_string() + } else { + "neither".to_string() + } + } + } + } +} + +pub fn gi_tsv_escape(v: String) -> String { + v1_rt::replace( + v1_rt::replace(v.clone(), "\t".to_string(), "\\t".to_string()), + "\n".to_string(), + "\\n".to_string(), + ) +} + +pub fn generic_identity_census_header() -> String { + "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label".to_string() +} + +pub fn gi_row_tsv(r: Rc) -> String { + Rc::new(vec![ + "observed".to_string(), + gi_tsv_escape(r.module_file.clone()), + gi_tsv_escape(r.enclosing.clone()), + r.carrier.clone(), + gi_tsv_escape(r.path.clone()), + gi_tsv_escape(r.parent_label.clone()), + gi_tsv_escape(r.spelled.clone()), + gi_bool_label(r.spelled_as_in_scope_generic.clone()), + gi_mark_label(r.clone()), + gi_tsv_escape(gi_declaration_label(r.declaration.clone())), + gi_tsv_escape(r.type_variable_id.clone()), + gi_tsv_escape(r.resolved_label.clone()), + ]) + .join(&"\t".to_string()) +} + +pub fn gi_unobserved_tsv(u: Rc) -> String { + Rc::new(vec![ + "UNOBSERVED".to_string(), + gi_tsv_escape(u.population.clone()), + gi_tsv_escape(u.reason.clone()), + gi_tsv_escape(u.would_observe.clone()), + ]) + .join(&"\t".to_string()) +} + +pub fn generic_identity_census_tsv(rows: Rc>>) -> String { + v1_rt::concat( + Rc::new(vec![generic_identity_census_header()]), + v1_rt::concat( + Rc::new({ + let mut __result = Vec::new(); + for u in generic_identity_unobserved().iter().cloned() { + __result.push(gi_unobserved_tsv(u.clone())); + } + __result + }), + Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + __result.push(gi_row_tsv(r.clone())); + } + __result + }), + ), + ) + .join(&"\n".to_string()) +} + +pub fn generic_identity_census_from_sources(sources: Rc>>) -> String { + { + let result = crate::v1_compiler_compile::compile_to_resolved(sources.clone()); + match result.graph.clone() { + std::option::Option::None => { + "REFUSED\tcompile_to_resolved produced no graph".to_string() + } + Some(g) => generic_identity_census_tsv(Rc::new({ + let mut __result = Vec::new(); + for m in g.modules.clone().iter().cloned() { + __result.extend( + (*gi_module_rows(m.clone(), result.source_indices.clone())) + .iter() + .cloned(), + ); + } + __result + })), + } + } +} diff --git a/src/v1/tests/claim/generic_identity_census.dag b/src/v1/tests/claim/generic_identity_census.dag new file mode 100644 index 00000000000..bb01d7bf402 --- /dev/null +++ b/src/v1/tests/claim/generic_identity_census.dag @@ -0,0 +1,354 @@ +module v1.tests.claim.generic_identity_census + +import v1.compiler.compile { SourceFile, ResolvedPipelineResult, compile_to_resolved } +import v1.compiler.infer_items { ResolvedGraph, TypedModule } +import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFormals, DeclarationBoundFormals, KernelGroundedFormals, LocalFormalsAwaitingModuleContext } +import v1.compiler.infer { param_is_generic_decl, type_node_label } +import v1.std.core { Node, NewlineIndex, ResolvedFormal, authored_name_at, param_node_type_expr, param_node_name_at, Resolved, TypeVariable, Arrow } +import std.decl_ref { DeclarationRef, TypeParameter, WholeDeclaration, NamedField, declaration_ref_display_key } +import std.types { List, String, Map, Bool } + +// THE STEP-2 INSTRUMENT OF docs/plans/derived-node-identity-design.md (section 7), AT OUTPUT GRAIN. +// For every type node the typed graph KEEPS -- a signature's parameter and return types, the three +// type fields of each ResolvedFormal, and the resolved type of every expression in a function body -- +// it records which generic-identity facts the node carries: the Node.declaration TypeParameter mark +// (v1.compiler.resolve binder_marked_type), the TypeVariable mark (v1.compiler.infer_env +// env_with_type_variable_bindings), both, or neither, beside how the node is spelled and what it +// resolves to. It reads; it changes no byte and edits no infer function. +// +// compile_to_resolved -> ResolvedPipelineResult.graph -> ResolvedGraph.modules +// -> TypedModule.func_env + TypedModule.items +// +// WHY OUTPUT GRAIN. The plan describes an event tap inside unify_generics and +// substitute_generics_apply. Those are pure folds with no output channel, and adding one would grow +// the seed to take a measurement (gentle-dove-36, 2026-10-04). The typed graph already holds the +// nodes those folds returned, so the question "which stored carriers hold a generic with no identity" +// is answerable by a reader, on the precedent of v1.tests.claim.carrier_realization_census. +// +// WHAT IT CANNOT SEE IS A TYPED POPULATION, NOT A ZERO. A node built and consumed inside one +// unification -- a call plan's formals, a substitution map's bindings -- never reaches the typed +// graph. Those populations are rows of GenericIdentityUnobserved, written into every receipt, so a +// reader cannot take their absence from the observed rows for a count of none. + +type GenericIdentityContext { + module_file: String + enclosing: String + generic_names: List + si: Map +} + +// ONE OBSERVED TYPE NODE. `spelled` is the node's own label and `resolved_label` the label of what it +// resolves to; they differ at a kernel container's element child, which is spelled after the +// container's template parameter and resolved to the element (the collision +// gunbc.recurring_failure_mode generic_identity_decided_by_spelling records). `parent_label` is empty +// at the root of a carrier. +type GenericIdentityRow { + module_file: String + enclosing: String + carrier: String + path: String + parent_label: String + spelled: String + spelled_as_in_scope_generic: Bool + declaration: DeclarationRef? + type_variable_id: String + carries_type_variable: Bool + resolved_label: String +} + +// A POPULATION THIS READER DOES NOT REACH. `would_observe` names what a measurement of it needs, so +// a bet whose answer lives here is reported undecided with its deciding instrument, never inferred. +type GenericIdentityUnobserved { + population: String + reason: String + would_observe: String +} + +fn generic_identity_unobserved() -> List { + [ + GenericIdentityUnobserved { + population: "call-plan formals built by v1.compiler.infer build_call_application_plan and bound by infer_call_arguments_generic_pass", + reason: "built and consumed inside one call's inference; the typed call node keeps the result type, not the plan", + would_observe: "an output on the call plan carrier, or a claim calling build_call_application_plan over supplied formals" + }, + GenericIdentityUnobserved { + population: "the formal and actual nodes each unify_generics call compares, and the bindings of its substitution map", + reason: "the map is local to the caller and is dropped after substitute_generics_apply returns", + would_observe: "a claim calling unify_generics and substitute_generics_apply over supplied nodes" + }, + GenericIdentityUnobserved { + population: "intermediate nodes of alias_chain_target_after_args, expand_alias_chain_for_field_access and record_lit_instantiated_fields", + reason: "only the final instantiated type is stored on the typed expression", + would_observe: "a claim calling each constructor over supplied nodes" + }, + GenericIdentityUnobserved { + population: "signatures whose formals are LocalFormalsAwaitingModuleContext", + reason: "the formals were never bound, so there is no ResolvedFormal to read; each such signature is one row with carrier formals_awaiting_module_context", + would_observe: "the same reader after bind_local_func_conformance has run for that signature" + } + ] +} + +fn gi_is_bare(n: Node) -> Bool { + (n.children |> count) == 0 && (n.params |> count) == 0 +} + +fn gi_declaration_is_type_parameter(d: DeclarationRef?) -> Bool { + match d { + Present { value: r } => + match r.field { + TypeParameter { name: _ } => true + WholeDeclaration => false + NamedField { field_name: _ } => false + } + Absent => false + } +} + +fn gi_type_variable_id(n: Node) -> String { + match n.inferred { + Present { value: TypeVariable { id: tv } } => tv + _ => "" + } +} + +fn gi_carries_type_variable(n: Node) -> Bool { + match n.inferred { + Present { value: TypeVariable { id: _ } } => true + _ => false + } +} + +fn gi_resolved_label(n: Node, si: Map) -> String { + match n.inferred { + Present { value: Resolved { node: rt } } => type_node_label(n: rt, source_indices: si) + _ => "" + } +} + +// A bare node is recorded when ANY identity question can be asked of it: it is spelled as a generic +// in scope, it carries either mark, or it sits in a type-argument position spelled one way and +// resolved another. A node meeting none of these is an ordinary concrete reference and is not a row. +fn gi_node_rows(ctx: GenericIdentityContext, carrier: String, path: String, parent_label: String, n: Node) -> List { + let spelled = type_node_label(n: n, source_indices: ctx.si) + let in_scope = ctx.generic_names |> any(g => g == spelled) + let resolved = gi_resolved_label(n: n, si: ctx.si) + let has_tv = gi_carries_type_variable(n: n) + let renamed_in_argument_position = parent_label != "" && resolved != "" && spelled != "" && resolved != spelled + if in_scope || has_tv || gi_declaration_is_type_parameter(d: n.declaration) || renamed_in_argument_position { + [GenericIdentityRow { + module_file: ctx.module_file, + enclosing: ctx.enclosing, + carrier: carrier, + path: path, + parent_label: parent_label, + spelled: spelled, + spelled_as_in_scope_generic: in_scope, + declaration: n.declaration, + type_variable_id: gi_type_variable_id(n: n), + carries_type_variable: has_tv, + resolved_label: resolved + }] + } else { + [] + } +} + +// The walk over ONE type. It descends type arguments, and an arrow's parameter and return positions, +// and never follows a reference's Resolved node into the declaration it names: that would walk every +// declaration once per reference. +fn gi_type_rows(ctx: GenericIdentityContext, carrier: String, path: String, parent_label: String, n: Node) -> List { + if gi_is_bare(n: n) { + gi_node_rows(ctx: ctx, carrier: carrier, path: path, parent_label: parent_label, n: n) + } else { + let own_label = type_node_label(n: n, source_indices: ctx.si) + let is_arrow = match n.connective { Arrow => true _ => false } + let child_rows = n.children |> flat_map(c => gi_type_rows(ctx: ctx, carrier: carrier, path: concat(path, "/arg"), parent_label: own_label, n: c)) + let param_rows = if is_arrow { + n.params |> flat_map(p => gi_type_rows(ctx: ctx, carrier: carrier, path: concat(path, "/fn_param"), parent_label: own_label, n: param_node_type_expr(n: p))) + } else { + [] + } + let return_rows = if is_arrow { + match n.inferred { + Present { value: Resolved { node: rt } } => gi_type_rows(ctx: ctx, carrier: carrier, path: concat(path, "/fn_return"), parent_label: own_label, n: rt) + _ => [] + } + } else { + [] + } + concat(concat(child_rows, param_rows), return_rows) + } +} + +fn gi_formal_rows(ctx: GenericIdentityContext, f: ResolvedFormal) -> List { + concat( + concat( + gi_type_rows(ctx: ctx, carrier: "formal_declared_type", path: f.parameter_identity, parent_label: "", n: f.declared_type), + gi_type_rows(ctx: ctx, carrier: "formal_declaration_bound_conformance", path: f.parameter_identity, parent_label: "", n: f.declaration_bound_conformance) + ), + gi_type_rows(ctx: ctx, carrier: "formal_substitution_basis", path: f.parameter_identity, parent_label: "", n: f.substitution_basis) + ) +} + +fn gi_awaiting_row(ctx: GenericIdentityContext) -> GenericIdentityRow { + GenericIdentityRow { + module_file: ctx.module_file, + enclosing: ctx.enclosing, + carrier: "formals_awaiting_module_context", + path: "", + parent_label: "", + spelled: "", + spelled_as_in_scope_generic: false, + declaration: none, + type_variable_id: "", + carries_type_variable: false, + resolved_label: "" + } +} + +fn gi_formals_rows(ctx: GenericIdentityContext, formals: ResolvedFormals) -> List { + match formals { + DeclarationBoundFormals { formals: fs } => fs |> flat_map(f => gi_formal_rows(ctx: ctx, f: f)) + KernelGroundedFormals { formals: fs } => fs |> flat_map(f => gi_formal_rows(ctx: ctx, f: f)) + LocalFormalsAwaitingModuleContext {} => [gi_awaiting_row(ctx: ctx)] + } +} + +fn gi_generic_names(params: List, si: Map) -> List { + params |> filter(p => param_is_generic_decl(p: p, source_indices: si)) |> map(p => param_node_name_at(n: p, source_indices: si)) +} + +// A SIGNATURE WITH NO TYPE PARAMETER IS NOT READ. Its formals name no generic of its own, and a +// kernel element child inside them is the same population the body walk reads at every expression. +fn gi_sig_rows(module_file: String, sig: ResolvedFuncSig, si: Map) -> List { + let names = gi_generic_names(params: sig.params, si: si) + if (names |> count) == 0 { + [] + } else { + let ctx = GenericIdentityContext { module_file: module_file, enclosing: sig.name, generic_names: names, si: si } + let value_params = sig.params |> filter(p => !param_is_generic_decl(p: p, source_indices: si)) + let param_rows = value_params |> flat_map(p => + gi_type_rows(ctx: ctx, carrier: "signature_param_type", path: param_node_name_at(n: p, source_indices: si), parent_label: "", n: param_node_type_expr(n: p))) + let return_rows = gi_type_rows(ctx: ctx, carrier: "signature_return", path: "", parent_label: "", n: sig.inferred) + concat(concat(param_rows, return_rows), gi_formals_rows(ctx: ctx, formals: sig.resolved_formals)) + } +} + +// THE BODY WALK. Every node under a function body is visited as an expression: its resolved type, if +// it has one, is read as a type, and its children, params and body are visited in turn. The carrier +// names the expression by its own label so a reader can group by what built the type. +fn gi_expr_rows(ctx: GenericIdentityContext, e: Node) -> List { + let own = match e.inferred { + Present { value: Resolved { node: rt } } => + gi_type_rows(ctx: ctx, carrier: "body_expression_type", path: type_node_label(n: e, source_indices: ctx.si), parent_label: "", n: rt) + _ => [] + } + let child_rows = e.children |> flat_map(c => gi_expr_rows(ctx: ctx, e: c)) + let param_rows = e.params |> flat_map(p => gi_expr_rows(ctx: ctx, e: p)) + let body_rows = match e.body { + Present { value: b } => gi_expr_rows(ctx: ctx, e: b) + Absent => [] + } + concat(concat(concat(own, child_rows), param_rows), body_rows) +} + +fn gi_item_body_rows(module_file: String, item: Node, si: Map) -> List { + match item.body { + Absent => [] + Present { value: b } => + gi_expr_rows( + ctx: GenericIdentityContext { + module_file: module_file, + enclosing: authored_name_at(source_indices: si, node: item), + generic_names: gi_generic_names(params: item.params, si: si), + si: si + }, + e: b + ) + } +} + +fn gi_module_rows(m: TypedModule, si: Map) -> List { + let module_file = authored_name_at(source_indices: si, node: m.module) + let sig_rows = map_values(m.func_env.local) |> flat_map(sig => gi_sig_rows(module_file: module_file, sig: sig, si: si)) + let body_rows = m.items |> flat_map(item => gi_item_body_rows(module_file: module_file, item: item, si: si)) + concat(sig_rows, body_rows) +} + +fn gi_bool_label(b: Bool) -> String { + if b { "yes" } else { "no" } +} + +fn gi_declaration_label(d: DeclarationRef?) -> String { + match d { + Present { value: r } => declaration_ref_display_key(ref: r) + Absent => "none" + } +} + +// THE MARK COLUMN IS DERIVED FROM THE TWO FACTS BESIDE IT, never stored on the row: a stored verdict +// next to the facts that produce it is a second authority for one fact. +fn gi_mark_label(r: GenericIdentityRow) -> String { + let has_decl = gi_declaration_is_type_parameter(d: r.declaration) + if has_decl && r.carries_type_variable { + "declaration+type_variable" + } else if has_decl { + "declaration_only" + } else if r.carries_type_variable { + "type_variable_only" + } else { + "neither" + } +} + +fn gi_tsv_escape(v: String) -> String { + replace(replace(v, "\t", "\\t"), "\n", "\\n") +} + +fn generic_identity_census_header() -> String { + "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label" +} + +fn gi_row_tsv(r: GenericIdentityRow) -> String { + [ + "observed", + gi_tsv_escape(v: r.module_file), + gi_tsv_escape(v: r.enclosing), + r.carrier, + gi_tsv_escape(v: r.path), + gi_tsv_escape(v: r.parent_label), + gi_tsv_escape(v: r.spelled), + gi_bool_label(b: r.spelled_as_in_scope_generic), + gi_mark_label(r: r), + gi_tsv_escape(v: gi_declaration_label(d: r.declaration)), + gi_tsv_escape(v: r.type_variable_id), + gi_tsv_escape(v: r.resolved_label) + ] |> join(separator: "\t") +} + +fn gi_unobserved_tsv(u: GenericIdentityUnobserved) -> String { + [ + "UNOBSERVED", + gi_tsv_escape(v: u.population), + gi_tsv_escape(v: u.reason), + gi_tsv_escape(v: u.would_observe) + ] |> join(separator: "\t") +} + +fn generic_identity_census_tsv(rows: List) -> String { + concat([generic_identity_census_header()], concat(generic_identity_unobserved() |> map(u => gi_unobserved_tsv(u: u)), rows |> map(r => gi_row_tsv(r: r)))) + |> join(separator: "\n") +} + +// THE ENTRY THE DRIVER CALLS. Sources in as data, receipt out as a String. A compile that produces +// no graph REFUSES in the return value, so a receipt that could not be produced and a receipt with no +// observed rows do not share a spelling. +fn generic_identity_census_from_sources(sources: List) -> String { + let result = compile_to_resolved(sources: sources) + match result.graph { + Absent => "REFUSED\tcompile_to_resolved produced no graph" + Present { value: g } => + generic_identity_census_tsv(rows: g.modules |> flat_map(m => gi_module_rows(m: m, si: result.source_indices))) + } +} From 675d15b65ff5f5171a0df3ee9cbe86aded232d43 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 4 Oct 2026 14:20:16 +0000 Subject: [PATCH 02/11] WIP: foreign-parameter column separates substitution loss from unbound-for-want-of-context --- .../v1_tests_claim_generic_identity_census.rs | 230 ++++++++++++++++-- .../tests/claim/generic_identity_census.dag | 112 ++++++++- 2 files changed, 316 insertions(+), 26 deletions(-) diff --git a/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs b/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs index f9531ce694f..eae081230e7 100644 --- a/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs +++ b/src/v1/stage0/src/v1_tests_claim_generic_identity_census.rs @@ -1,6 +1,7 @@ // Generated by v1 compiler -- do not edit. // Source module: v1.tests.claim.generic_identity_census +use self::ForeignParameterReading::*; pub use crate::std_decl_ref::declaration_ref_display_key; use crate::std_decl_ref::DeclField::{NamedField, TypeParameter, WholeDeclaration}; pub use crate::std_decl_ref::{DeclField, DeclarationRef}; @@ -30,6 +31,18 @@ pub struct GenericIdentityContext { pub enclosing: String, pub generic_names: Rc>, pub si: Rc>>, + pub sigs: Rc>>, +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(tag = "_variant")] +pub enum ForeignParameterReading { + OwnParameterOrUnmarked, + ForeignOwnerIsNotAFunction, + ForeignParameterInAValueArgument, + ForeignParameterInNoValueArgument, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -45,6 +58,7 @@ pub struct GenericIdentityRow { pub type_variable_id: String, pub carries_type_variable: bool, pub resolved_label: String, + pub foreign: ForeignParameterReading, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -89,6 +103,125 @@ pub fn gi_declaration_is_type_parameter(d: Option>) -> bool { } } +pub fn gi_declaration_key(d: Option>) -> String { + match d.clone() { + Some(r) => crate::std_decl_ref::declaration_ref_display_key(r.clone()), + std::option::Option::None => "".to_string(), + } +} + +pub fn gi_type_mentions_parameter(n: Rc, key: String) -> bool { + stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { + if gi_is_bare(n.clone()) { + (gi_declaration_is_type_parameter(n.declaration.clone()) + && (gi_declaration_key(n.declaration.clone()) == key.clone())) + } else { + { + let is_arrow = match n.connective.clone() { + Connective::Arrow => true, + _ => false, + }; + let in_children = { + let mut __found = false; + for c in n.children.clone().iter().cloned() { + if gi_type_mentions_parameter(c.clone(), key.clone()) { + __found = true; + break; + } + } + __found + }; + let in_params = (is_arrow.clone() && { + let mut __found = false; + for p in n.params.clone().iter().cloned() { + if gi_type_mentions_parameter( + crate::v1_std_core::param_node_type_expr(p.clone()), + key.clone(), + ) { + __found = true; + break; + } + } + __found + }); + let in_return = (is_arrow.clone() + && match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => { + gi_type_mentions_parameter(rt.clone(), key.clone()) + } + _ => false, + }); + ((in_children.clone() || in_params.clone()) || in_return.clone()) + } + } + }) +} + +pub fn gi_sig_key(module_path: String, decl_name: String) -> String { + v1_rt::concat( + v1_rt::concat(module_path.clone(), "::".to_string()), + decl_name.clone(), + ) +} + +pub fn gi_foreign_reading(ctx: Rc, n: Rc) -> ForeignParameterReading { + match n.declaration.clone() { + std::option::Option::None => ForeignParameterReading::OwnParameterOrUnmarked, + Some(r) => { + if !gi_declaration_is_type_parameter(n.declaration.clone()) { + ForeignParameterReading::OwnParameterOrUnmarked + } else { + if ((r.module_path.clone() == ctx.module_file.clone()) + && (r.decl_name.clone() == ctx.enclosing.clone())) + { + ForeignParameterReading::OwnParameterOrUnmarked + } else { + match v1_rt::map_get( + &ctx.sigs.clone(), + gi_sig_key(r.module_path.clone(), r.decl_name.clone()), + ) { + std::option::Option::None => { + ForeignParameterReading::ForeignOwnerIsNotAFunction + } + Some(sig) => { + let key = crate::std_decl_ref::declaration_ref_display_key(r.clone()); + let value_params = Rc::new({ + let mut __result = Vec::new(); + for p in sig.params.clone().iter().cloned() { + if !crate::v1_compiler_infer::param_is_generic_decl( + p.clone(), + ctx.si.clone(), + ) { + __result.push(p); + } + } + __result + }); + if { + let mut __found = false; + for p in value_params.iter().cloned() { + if gi_type_mentions_parameter( + crate::v1_std_core::param_node_type_expr(p.clone()), + key.clone(), + ) { + __found = true; + break; + } + } + __found + } { + ForeignParameterReading::ForeignParameterInAValueArgument + } else { + ForeignParameterReading::ForeignParameterInNoValueArgument + } + } + } + } + } + } + } +} + pub fn gi_type_variable_id(n: Rc) -> String { match n.inferred.clone().as_deref().cloned() { Some(InferredNode::TypeVariable { id: tv, .. }) => tv.clone(), @@ -153,6 +286,7 @@ pub fn gi_node_rows( type_variable_id: gi_type_variable_id(n.clone()), carries_type_variable: has_tv.clone(), resolved_label: resolved.clone(), + foreign: gi_foreign_reading(ctx.clone(), n.clone()), })]) } else { Rc::new(vec![]) @@ -289,6 +423,7 @@ pub fn gi_awaiting_row(ctx: Rc) -> Rc, si: Rc>>, + sigs: Rc>>, ) -> Rc>> { { let names = gi_generic_names(sig.params.clone(), si.clone()); @@ -360,6 +496,7 @@ pub fn gi_sig_rows( enclosing: sig.name.clone(), generic_names: names.clone(), si: si.clone(), + sigs: sigs.clone(), }); let value_params = Rc::new({ let mut __result = Vec::new(); @@ -450,6 +587,7 @@ pub fn gi_item_body_rows( module_file: String, item: Rc, si: Rc>>, + sigs: Rc>>, ) -> Rc>> { match item.body.clone() { std::option::Option::None => Rc::new(vec![]), @@ -459,6 +597,7 @@ pub fn gi_item_body_rows( enclosing: crate::v1_std_core::authored_name_at(si.clone(), item.clone()), generic_names: gi_generic_names(item.params.clone(), si.clone()), si: si.clone(), + sigs: sigs.clone(), }), b.clone(), ), @@ -468,6 +607,7 @@ pub fn gi_item_body_rows( pub fn gi_module_rows( m: Rc, si: Rc>>, + sigs: Rc>>, ) -> Rc>> { { let module_file = crate::v1_std_core::authored_name_at(si.clone(), m.module.clone()); @@ -478,7 +618,7 @@ pub fn gi_module_rows( .cloned() { __result.extend( - (*gi_sig_rows(module_file.clone(), sig.clone(), si.clone())) + (*gi_sig_rows(module_file.clone(), sig.clone(), si.clone(), sigs.clone())) .iter() .cloned(), ); @@ -489,9 +629,14 @@ pub fn gi_module_rows( let mut __result = Vec::new(); for item in m.items.clone().iter().cloned() { __result.extend( - (*gi_item_body_rows(module_file.clone(), item.clone(), si.clone())) - .iter() - .cloned(), + (*gi_item_body_rows( + module_file.clone(), + item.clone(), + si.clone(), + sigs.clone(), + )) + .iter() + .cloned(), ); } __result @@ -500,6 +645,46 @@ pub fn gi_module_rows( } } +pub fn gi_all_sigs( + modules: Rc>>, + si: Rc>>, +) -> Rc>> { + modules.iter().cloned().fold( + v1_rt::rc_empty_map::>(), + |acc: Rc>>, m: Rc| { + let module_file = crate::v1_std_core::authored_name_at(si.clone(), m.module.clone()); + Rc::new(v1_rt::map_values(&m.func_env.clone().local.clone())) + .iter() + .cloned() + .fold( + acc, + |inner: Rc>>, sig: Rc| { + v1_rt::rc_map_insert( + inner, + gi_sig_key(module_file.clone(), sig.name.clone()), + sig.clone(), + ) + }, + ) + }, + ) +} + +pub fn gi_foreign_label(f: ForeignParameterReading) -> String { + match f.clone() { + ForeignParameterReading::OwnParameterOrUnmarked => "own_or_unmarked".to_string(), + ForeignParameterReading::ForeignOwnerIsNotAFunction => { + "foreign_owner_not_a_function".to_string() + } + ForeignParameterReading::ForeignParameterInAValueArgument => { + "foreign_in_a_value_argument".to_string() + } + ForeignParameterReading::ForeignParameterInNoValueArgument => { + "foreign_in_no_value_argument".to_string() + } + } +} + pub fn gi_bool_label(b: bool) -> String { if b.clone() { "yes".to_string() @@ -543,7 +728,7 @@ pub fn gi_tsv_escape(v: String) -> String { } pub fn generic_identity_census_header() -> String { - "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label".to_string() + "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label\tforeign_parameter".to_string() } pub fn gi_row_tsv(r: Rc) -> String { @@ -560,6 +745,7 @@ pub fn gi_row_tsv(r: Rc) -> String { gi_tsv_escape(gi_declaration_label(r.declaration.clone())), gi_tsv_escape(r.type_variable_id.clone()), gi_tsv_escape(r.resolved_label.clone()), + gi_foreign_label(r.foreign.clone()), ]) .join(&"\t".to_string()) } @@ -604,17 +790,33 @@ pub fn generic_identity_census_from_sources(sources: Rc>>) -> std::option::Option::None => { "REFUSED\tcompile_to_resolved produced no graph".to_string() } - Some(g) => generic_identity_census_tsv(Rc::new({ - let mut __result = Vec::new(); - for m in g.modules.clone().iter().cloned() { - __result.extend( - (*gi_module_rows(m.clone(), result.source_indices.clone())) + Some(g) => { + let sigs = gi_all_sigs(g.modules.clone(), result.source_indices.clone()); + generic_identity_census_tsv(Rc::new({ + let mut __result = Vec::new(); + for m in g.modules.clone().iter().cloned() { + __result.extend( + (*gi_module_rows( + m.clone(), + result.source_indices.clone(), + sigs.clone(), + )) .iter() .cloned(), - ); - } - __result - })), + ); + } + __result + })) + } } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct OwnParameterOrUnmarked; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ForeignOwnerIsNotAFunction; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ForeignParameterInAValueArgument; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ForeignParameterInNoValueArgument; diff --git a/src/v1/tests/claim/generic_identity_census.dag b/src/v1/tests/claim/generic_identity_census.dag index bb01d7bf402..72b211e6de1 100644 --- a/src/v1/tests/claim/generic_identity_census.dag +++ b/src/v1/tests/claim/generic_identity_census.dag @@ -35,8 +35,21 @@ type GenericIdentityContext { enclosing: String generic_names: List si: Map + sigs: Map } +// WHOSE PARAMETER A MARKED NODE NAMES, RELATIVE TO WHERE IT WAS FOUND. A node carrying another +// declaration's TypeParameter mark is that declaration's parameter left unsubstituted, and there are +// two causes that must not share a count. Where a value argument of the owner mentions the +// parameter, an argument could have bound it and did not: a substitution loss. Where none does (a +// nullary generic constructor such as an empty map), only an expected type could bind it: unbound +// for want of context, which is a different defect with a different repair. +type ForeignParameterReading + = OwnParameterOrUnmarked + | ForeignOwnerIsNotAFunction + | ForeignParameterInAValueArgument + | ForeignParameterInNoValueArgument + // ONE OBSERVED TYPE NODE. `spelled` is the node's own label and `resolved_label` the label of what it // resolves to; they differ at a kernel container's element child, which is spelled after the // container's template parameter and resolved to the element (the collision @@ -54,6 +67,7 @@ type GenericIdentityRow { type_variable_id: String carries_type_variable: Bool resolved_label: String + foreign: ForeignParameterReading } // A POPULATION THIS READER DOES NOT REACH. `would_observe` names what a measurement of it needs, so @@ -105,6 +119,57 @@ fn gi_declaration_is_type_parameter(d: DeclarationRef?) -> Bool { } } +fn gi_declaration_key(d: DeclarationRef?) -> String { + match d { + Present { value: r } => declaration_ref_display_key(ref: r) + Absent => "" + } +} + +// Whether a type mentions the parameter named by `key`, by the mark and never by the spelling. +fn gi_type_mentions_parameter(n: Node, key: String) -> Bool { + if gi_is_bare(n: n) { + gi_declaration_is_type_parameter(d: n.declaration) && gi_declaration_key(d: n.declaration) == key + } else { + let is_arrow = match n.connective { Arrow => true _ => false } + let in_children = n.children |> any(c => gi_type_mentions_parameter(n: c, key: key)) + let in_params = is_arrow && (n.params |> any(p => gi_type_mentions_parameter(n: param_node_type_expr(n: p), key: key))) + let in_return = is_arrow && match n.inferred { + Present { value: Resolved { node: rt } } => gi_type_mentions_parameter(n: rt, key: key) + _ => false + } + in_children || in_params || in_return + } +} + +fn gi_sig_key(module_path: String, decl_name: String) -> String { + concat(concat(module_path, "::"), decl_name) +} + +fn gi_foreign_reading(ctx: GenericIdentityContext, n: Node) -> ForeignParameterReading { + match n.declaration { + Absent => OwnParameterOrUnmarked + Present { value: r } => + if !gi_declaration_is_type_parameter(d: n.declaration) { + OwnParameterOrUnmarked + } else if (r.module_path as String) == ctx.module_file && (r.decl_name as String) == ctx.enclosing { + OwnParameterOrUnmarked + } else { + match map_get(ctx.sigs, gi_sig_key(module_path: r.module_path as String, decl_name: r.decl_name as String)) { + Absent => ForeignOwnerIsNotAFunction + Present { value: sig } => + let key = declaration_ref_display_key(ref: r) + let value_params = sig.params |> filter(p => !param_is_generic_decl(p: p, source_indices: ctx.si)) + if value_params |> any(p => gi_type_mentions_parameter(n: param_node_type_expr(n: p), key: key)) { + ForeignParameterInAValueArgument + } else { + ForeignParameterInNoValueArgument + } + } + } + } +} + fn gi_type_variable_id(n: Node) -> String { match n.inferred { Present { value: TypeVariable { id: tv } } => tv @@ -147,7 +212,8 @@ fn gi_node_rows(ctx: GenericIdentityContext, carrier: String, path: String, pare declaration: n.declaration, type_variable_id: gi_type_variable_id(n: n), carries_type_variable: has_tv, - resolved_label: resolved + resolved_label: resolved, + foreign: gi_foreign_reading(ctx: ctx, n: n) }] } else { [] @@ -203,7 +269,8 @@ fn gi_awaiting_row(ctx: GenericIdentityContext) -> GenericIdentityRow { declaration: none, type_variable_id: "", carries_type_variable: false, - resolved_label: "" + resolved_label: "", + foreign: OwnParameterOrUnmarked } } @@ -221,12 +288,12 @@ fn gi_generic_names(params: List, si: Map) -> List) -> List { +fn gi_sig_rows(module_file: String, sig: ResolvedFuncSig, si: Map, sigs: Map) -> List { let names = gi_generic_names(params: sig.params, si: si) if (names |> count) == 0 { [] } else { - let ctx = GenericIdentityContext { module_file: module_file, enclosing: sig.name, generic_names: names, si: si } + let ctx = GenericIdentityContext { module_file: module_file, enclosing: sig.name, generic_names: names, si: si, sigs: sigs } let value_params = sig.params |> filter(p => !param_is_generic_decl(p: p, source_indices: si)) let param_rows = value_params |> flat_map(p => gi_type_rows(ctx: ctx, carrier: "signature_param_type", path: param_node_name_at(n: p, source_indices: si), parent_label: "", n: param_node_type_expr(n: p))) @@ -253,7 +320,7 @@ fn gi_expr_rows(ctx: GenericIdentityContext, e: Node) -> List) -> List { +fn gi_item_body_rows(module_file: String, item: Node, si: Map, sigs: Map) -> List { match item.body { Absent => [] Present { value: b } => @@ -262,20 +329,39 @@ fn gi_item_body_rows(module_file: String, item: Node, si: Map) -> List { +fn gi_module_rows(m: TypedModule, si: Map, sigs: Map) -> List { let module_file = authored_name_at(source_indices: si, node: m.module) - let sig_rows = map_values(m.func_env.local) |> flat_map(sig => gi_sig_rows(module_file: module_file, sig: sig, si: si)) - let body_rows = m.items |> flat_map(item => gi_item_body_rows(module_file: module_file, item: item, si: si)) + let sig_rows = map_values(m.func_env.local) |> flat_map(sig => gi_sig_rows(module_file: module_file, sig: sig, si: si, sigs: sigs)) + let body_rows = m.items |> flat_map(item => gi_item_body_rows(module_file: module_file, item: item, si: si, sigs: sigs)) concat(sig_rows, body_rows) } +// Every function signature of the compiled graph, keyed by (module, name), built once per run so a +// foreign parameter's owner is found by lookup and not by a scan per row. +fn gi_all_sigs(modules: List, si: Map) -> Map { + fold(modules, init: empty_map(), f: (acc, m) => + let module_file = authored_name_at(source_indices: si, node: m.module) + fold(map_values(m.func_env.local), init: acc, f: (inner, sig) => + map_insert(inner, gi_sig_key(module_path: module_file, decl_name: sig.name), sig))) +} + +fn gi_foreign_label(f: ForeignParameterReading) -> String { + match f { + OwnParameterOrUnmarked => "own_or_unmarked" + ForeignOwnerIsNotAFunction => "foreign_owner_not_a_function" + ForeignParameterInAValueArgument => "foreign_in_a_value_argument" + ForeignParameterInNoValueArgument => "foreign_in_no_value_argument" + } +} + fn gi_bool_label(b: Bool) -> String { if b { "yes" } else { "no" } } @@ -307,7 +393,7 @@ fn gi_tsv_escape(v: String) -> String { } fn generic_identity_census_header() -> String { - "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label" + "kind\tmodule_file\tenclosing\tcarrier\tpath\tparent_label\tspelled\tspelled_as_in_scope_generic\tmark\tdeclaration\ttype_variable_id\tresolved_label\tforeign_parameter" } fn gi_row_tsv(r: GenericIdentityRow) -> String { @@ -323,7 +409,8 @@ fn gi_row_tsv(r: GenericIdentityRow) -> String { gi_mark_label(r: r), gi_tsv_escape(v: gi_declaration_label(d: r.declaration)), gi_tsv_escape(v: r.type_variable_id), - gi_tsv_escape(v: r.resolved_label) + gi_tsv_escape(v: r.resolved_label), + gi_foreign_label(f: r.foreign) ] |> join(separator: "\t") } @@ -349,6 +436,7 @@ fn generic_identity_census_from_sources(sources: List) -> String { match result.graph { Absent => "REFUSED\tcompile_to_resolved produced no graph" Present { value: g } => - generic_identity_census_tsv(rows: g.modules |> flat_map(m => gi_module_rows(m: m, si: result.source_indices))) + let sigs = gi_all_sigs(modules: g.modules, si: result.source_indices) + generic_identity_census_tsv(rows: g.modules |> flat_map(m => gi_module_rows(m: m, si: result.source_indices, sigs: sigs))) } } From 8d21729d5db2df085d1638bf3b2b874c91cc6b62 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 4 Oct 2026 14:36:40 +0000 Subject: [PATCH 03/11] WIP: generic-identity-census label, controls, summary, host producer arm --- .../generic_identity_census_seed_growth.dag | 23 ++ dag/gunbc/instrument_targets.dag | 21 +- dag/gunbc/seed_growth_admission.dag | 2 + dag/gunbc/target_binding.dag | 1 + .../claim/target_invocation_witness_test.dag | 13 + src/v1/stage0/src/target_invocation_host.rs | 67 ++++ .../v1_tests_claim_generic_identity_census.rs | 302 +++++++++++++++++- .../tests/claim/generic_identity_census.dag | 105 +++++- 8 files changed, 516 insertions(+), 18 deletions(-) create mode 100644 dag/gunbc/generic_identity_census_seed_growth.dag diff --git a/dag/gunbc/generic_identity_census_seed_growth.dag b/dag/gunbc/generic_identity_census_seed_growth.dag new file mode 100644 index 00000000000..0eb0ec81329 --- /dev/null +++ b/dag/gunbc/generic_identity_census_seed_growth.dag @@ -0,0 +1,23 @@ +module gunbc.generic_identity_census_seed_growth + +import gunbc.roadmap_model { RoadmapNodeId } +import gunbc.seed_growth { SeedGrowthJustification } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data generic_identity_census_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { + hand_authored_declarations: [ + DeclarationRef { + module_path: "v1_compiler.target_invocation_host", + decl_name: "run_generic_identity_census", + field: WholeDeclaration + } + ], + + reason: "The producer arm for the derived-node-identity step-2 instrument (gunbc.target_binding GenericIdentityCensusProducer). Its host part is transport only: read the fixture file and the v2.compiler.compile closure's sources from disk, hand each to v1.tests.claim.generic_identity_census as SourceFile data, and relay the returned text. The standing line, every control, the report's counts and the unobserved populations are all decided by that .dag module; the host maps the first line of the returned standing to a termination and adds no verdict. It sits in the host because compile_to_resolved over an authored source vector does not run under the interpreter (the same boundary v1.tests.claim.carrier_realization_census records for its own driver).", + + owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, + + trigger: "Delete this declaration when gunbc test's producer dispatch is emitted rather than hand-mirrored (the capability gunbc.target_invocation_seed_growth names for target_invocation_host as a whole) AND an emitted driver can read source files and hand them to a .dag fold, SUFFICIENT FOR //gunbc/instruments:generic-identity-census to run with no hand-authored producer body.", + + current_boundary: "src/v1/stage0/src/target_invocation_host.rs run_generic_identity_census, which reads through the existing cli_run load_sources_for_entry_with_pool_index, behind gunbc test //gunbc/instruments:generic-identity-census" +} diff --git a/dag/gunbc/instrument_targets.dag b/dag/gunbc/instrument_targets.dag index 3bdb6debf9d..8d47129963c 100644 --- a/dag/gunbc/instrument_targets.dag +++ b/dag/gunbc/instrument_targets.dag @@ -52,6 +52,7 @@ import gunbc.target_binding { SelfHostBehavioralEquivalenceProducer, V2NativeCensusProducer, ResolveRefusalReading, TypeDeclarationUseReading, DependencyDemandCensusProducer, + GenericIdentityCensusProducer, BareChannelEligibility, BareChannelRuns, BareChannelDisabledByImportLine, BareReferenceChannelEntryReading, BareReferenceChannelExpectation, BareReferenceChannelStanding, BareReferenceChannelRead, @@ -461,6 +462,18 @@ fn dependency_demand_census_label() -> Label { Label { package: instruments_package() target: TargetName { name: "dependency-demand-census" } } } +// THE DERIVED-NODE-IDENTITY STEP-2 INSTRUMENT (docs/plans/derived-node-identity-design.md section 7; +// class gunbc.recurring_failure_mode generic_identity_decided_by_spelling). The seed's own +// v1.tests.claim.generic_identity_census reads the typed graph and reports, per stored type node, +// which generic-identity mark it carries. The standing is decided by the census's controls over +// fixtures/generic_identity_census; the report beside it covers the v2.compiler.compile closure. It +// states the populations it cannot reach in every run and never renders them as a count of none. +// Run by name, on no lane: its consumers are the step-2 changes, each of which executes it at its +// own head. `gunbc test //gunbc/instruments:generic-identity-census`. +fn generic_identity_census_label() -> Label { + Label { package: instruments_package() target: TargetName { name: "generic-identity-census" } } +} + // THE INSTRUMENT IS A `TestRule` WITH NO DEPENDENCIES, and both halves are measurements rather // than placeholders. It answers a test question — does the heads reading read the same grammar as // the full reading — so it is not a `BuildRule`; and it depends on no other target in this graph, @@ -545,6 +558,10 @@ fn dependency_demand_census_binding() -> TargetBinding { TargetBinding { target: dependency_demand_census_label() producer: DependencyDemandCensusProducer {} } } +fn generic_identity_census_binding() -> TargetBinding { + TargetBinding { target: generic_identity_census_label() producer: GenericIdentityCensusProducer {} } +} + fn emitted_crate_workspace_binding() -> TargetBinding { TargetBinding { target: emitted_crate_workspace_label() producer: EmittedCrateWorkspaceProducer {} } } @@ -726,7 +743,7 @@ fn instrument_targets() -> List