Skip to content
Merged
2,827 changes: 1,415 additions & 1,412 deletions src/v3/compiler/src/bootstrap_generated.rs

Large diffs are not rendered by default.

2,661 changes: 1,332 additions & 1,329 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs

Large diffs are not rendered by default.

200 changes: 198 additions & 2 deletions src/v3/compiler/tests/integration/method_template_contract_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
//! Substrate method registry)
//! - `method_template_contract_does_not_carry_cost_data`

use std::collections::HashSet;
use v3_compiler::dag::{Dag, DeclarationId, FieldValue, TypeConnective, ValueBody};
use std::collections::{HashMap, HashSet};
use v3_compiler::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody};
use v3_compiler::generated_full_bootstrap_dag;

fn conj_field_labels(dag: &Dag, name: &str) -> Vec<String> {
Expand All @@ -33,6 +33,22 @@ fn decl_id_by_name(dag: &Dag, name: &str) -> DeclarationId {
.id
}

fn conj_field_ty(dag: &Dag, name: &str, field: &str) -> DeclarationId {
let decl = dag
.declaration_by_name(name)
.unwrap_or_else(|| panic!("`{name}` missing from full bootstrap"));
match &decl.connective {
TypeConnective::Conj { children } => {
children
.iter()
.find(|f| f.label == field)
.unwrap_or_else(|| panic!("`{name}` missing `{field}` field"))
.ty
}
other => panic!("`{name}` is not a Conj: {other:?}"),
}
}

#[test]
fn method_template_contract_distinct_from_method_contract() {
let dag = generated_full_bootstrap_dag();
Expand Down Expand Up @@ -93,6 +109,46 @@ fn method_template_contract_does_not_carry_cost_data() {
);
}

#[test]
fn method_template_contract_emit_template_uses_sum_not_nullable_bridge_fields() {
let dag = generated_full_bootstrap_dag();
let emit_template_ty = conj_field_ty(&dag, "MethodTemplateContract", "emit_template");
assert_eq!(
dag.declaration(emit_template_ty).name.as_deref(),
Some("MethodEmitTemplate"),
"MethodTemplateContract.emit_template must point at the sum carrier, not raw String"
);

let TypeConnective::Disj { variants } = &dag.declaration(emit_template_ty).connective else {
panic!("MethodEmitTemplate must be a Disj");
};
let labels: HashSet<&str> = variants.iter().map(|v| v.label.as_str()).collect();
assert_eq!(
labels,
["SingleTemplate", "HigherOrderTemplates"]
.into_iter()
.collect(),
"MethodEmitTemplate must keep the ordinary vs higher-order split explicit"
);

let contract_fields: HashSet<String> = conj_field_labels(&dag, "MethodTemplateContract")
.into_iter()
.collect();
for forbidden in [
"inline_template",
"fn_ref_template",
"wraps_in_sharing",
"emit_template_inline",
"emit_template_fn_ref",
] {
assert!(
!contract_fields.contains(forbidden),
"MethodTemplateContract grew nullable/parallel higher-order field `{forbidden}` \
instead of using MethodEmitTemplate"
);
}
}

/// Walk a per-target `List<MethodTemplateContract>` declaration's value body
/// and assert that every row's `dag_method: DeclarationRef` is unique within
/// the list. Empty lists vacuously pass (no rows to compare); once Substrate's
Expand Down Expand Up @@ -152,6 +208,146 @@ fn assert_per_target_list_dag_method_unique(dag: &Dag, list_name: &str) {
}
}

fn method_ref_decl_from_row<'a>(
row: &'a FieldValue,
row_context: &str,
) -> (&'a DeclarationId, &'a FieldValue, &'a FieldValue) {
let FieldValue::Record(fields) = row else {
panic!("{row_context}: row is not a FieldValue::Record");
};
let (_, dag_method) = fields
.iter()
.find(|(label, _)| label == "dag_method")
.unwrap_or_else(|| panic!("{row_context}: missing `dag_method` field"));
let FieldValue::Record(method_ref_fields) = dag_method else {
panic!("{row_context}: `dag_method` is not MethodRef record");
};
let (_, decl_field) = method_ref_fields
.iter()
.find(|(label, _)| label == "decl")
.unwrap_or_else(|| panic!("{row_context}: MethodRef missing `decl` field"));
let FieldValue::Reference(decl_id) = decl_field else {
panic!("{row_context}: MethodRef.decl is not a reference");
};
let (_, emit_template) = fields
.iter()
.find(|(label, _)| label == "emit_template")
.unwrap_or_else(|| panic!("{row_context}: missing `emit_template` field"));
let (_, wraps_result) = fields
.iter()
.find(|(label, _)| label == "wraps_result")
.unwrap_or_else(|| panic!("{row_context}: missing `wraps_result` field"));
(decl_id, emit_template, wraps_result)
}

fn method_emit_template_variant_label(dag: &Dag, constructor: DeclarationId) -> &str {
let method_emit_template = dag
.declaration_by_name("MethodEmitTemplate")
.expect("MethodEmitTemplate");
let TypeConnective::Disj { variants } = &method_emit_template.connective else {
panic!("MethodEmitTemplate must be a Disj");
};
variants
.iter()
.find(|variant| variant.ty == constructor)
.unwrap_or_else(|| panic!("unknown MethodEmitTemplate constructor {constructor:?}"))
.label
.as_str()
}

fn rust_method_template_rows(dag: &Dag) -> &[FieldValue] {
let decl = dag
.declaration_by_name("rust_method_template_contracts")
.expect("rust_method_template_contracts");
let body = decl.value_body.as_ref().expect("value body");
let ValueBody::List(rows) = body else {
panic!("rust_method_template_contracts must lower to ValueBody::List");
};
rows
}

#[test]
fn rust_higher_order_method_template_contracts_are_present() {
let dag = generated_full_bootstrap_dag();
let rows = rust_method_template_rows(&dag);
let mut seen = HashSet::new();
let expected_wraps: HashMap<&str, bool> = [
("filter_method", true),
("any_method", false),
("all_method", false),
("flat_map_method", true),
]
.into_iter()
.collect();

for (idx, row) in rows.iter().enumerate() {
let (decl_id, emit_template, wraps_result) =
method_ref_decl_from_row(row, &format!("rust row {idx}"));
let method_name = dag
.declaration(*decl_id)
.name
.as_deref()
.unwrap_or("<unnamed>");
if ![
"all_method",
"any_method",
"filter_method",
"flat_map_method",
]
.contains(&method_name)
{
continue;
}
seen.insert(method_name.to_string());
let FieldValue::Variant {
constructor,
payload,
} = emit_template
else {
panic!("{method_name}: emit_template must be MethodEmitTemplate variant");
};
assert_eq!(
method_emit_template_variant_label(&dag, *constructor),
"HigherOrderTemplates",
"{method_name} must use the higher-order template variant"
);
assert_eq!(
payload.len(),
2,
"{method_name}: HigherOrderTemplates must carry inline and fn-ref templates only"
);
assert!(
matches!(&payload[0], FieldValue::Literal(LiteralBits::String(s)) if s.contains("{param}") && s.contains("{iter}")),
"{method_name}: inline_template should preserve legacy inline placeholders"
);
assert!(
matches!(&payload[1], FieldValue::Literal(LiteralBits::String(s)) if s.contains("{arg}")),
"{method_name}: fn_ref_template should preserve legacy fn-ref placeholder"
);
let FieldValue::Literal(LiteralBits::Bool(row_wraps)) = wraps_result else {
panic!("{method_name}: wraps_result must be the row-level Bool wrapping authority");
};
assert!(
*row_wraps == expected_wraps[method_name],
"{method_name}: wraps_result must preserve legacy higher-order wrapping bit"
);
}

assert_eq!(
seen,
[
"all_method",
"any_method",
"filter_method",
"flat_map_method"
]
.into_iter()
.map(String::from)
.collect(),
"Rust MethodTemplateContract rows must include the four higher-order methods"
);
}

#[test]
fn method_template_contract_per_target_dag_method_unique() {
// Phase 1 (T-Ground-LanguageSpec scope E.1): per-target row lists landed
Expand Down
8 changes: 4 additions & 4 deletions src/v3/compiler/tests/integration/parse_corpus_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ src/v3/std/computation_model.dag 14 22953 20e458fe097d175a
src/v3/std/diagnostics.dag 10 12179 bc414c493551b464
src/v3/std/dimensions.dag 21 40239 d595746d2bda22f6
src/v3/std/effects.dag 53 97984 1acdbeaa68acfd2e
src/v3/std/emit_model.dag 35 65950 80de7505c56ef93a
src/v3/std/emit_model.dag 36 68760 30bb483bab671fa7
src/v3/std/extdeps_bootstrap_fixtures.dag 4 12090 e307b3796658c074
src/v3/std/go_method_template_contracts.dag 5 93546 b383eacaafd0dc1c
src/v3/std/go_method_template_contracts.dag 5 110898 7eb5c9f1296f4a45
src/v3/std/induction.dag 56 88794 546f74c63c189c50
src/v3/std/lens.dag 6 9852 28d5539a5b28716e
src/v3/std/list.dag 19 40269 8c648d4a4fd05c21
src/v3/std/lookup.dag 5 4734 f97d4e536b675b1b
src/v3/std/methods.dag 3 1388 48f145b21c115326
src/v3/std/parse_surface.dag 18 99510 b73114d5b178303d
src/v3/std/python_method_template_contracts.dag 5 120438 17c36841b44234d2
src/v3/std/python_method_template_contracts.dag 5 142882 3086daf4cf293650
src/v3/std/resources.dag 5 3632 5303e31e96b01ee5
src/v3/std/runtime.dag 6 7993 0e703b02069526f6
src/v3/std/rust_method_template_contracts.dag 5 61868 1e4c73f0fe7f03c6
src/v3/std/rust_method_template_contracts.dag 5 109753 7b10fcd0d9079900
src/v3/std/substrate.dag 59 128739 82987951254e7b4d
src/v3/std/substrate_minimal.dag 8 5372 feeb314c735f4672
src/v3/std/termination.dag 18 36478 d010232f0373dc99
Expand Down
25 changes: 24 additions & 1 deletion src/v3/std/emit_model.dag
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,29 @@ type PlaceholderConvention
= IndexedArgs
| NamedArg

// 🟢 TERMINAL at target method-emission-template scope. Method emit
// templates split structurally between ordinary single-expression
// renderings and higher-order method renderings that must preserve two
// target-specific templates: an inline-lambda body template and a
// function-reference adapter template. The row-level `wraps_result`
// remains the single wrapping authority for both cases; the higher-order
// variant intentionally does not carry a second wrapping bit. Pattern 1
// (merge into optional fields on `MethodTemplateContract`) fails because
// the two-template shape is a closed semantic case, not sparse data on
// every row. Pattern 2 (sibling carrier) fails because it would split the
// single per-method emit-template authority across parallel row lists.
// Pattern 3 (collapse into one string) would lose the inline-vs-fn-ref
// distinction carried by Rust's legacy `HigherOrderMethodSpec`. No
// dissolution trigger: later targets can attach to one of these durable
// cases, and any richer typed template-segment substrate refines inside
// the variant payloads rather than replacing the coproduct.
type MethodEmitTemplate
= SingleTemplate { template: String }
| HigherOrderTemplates {
inline_template: String
fn_ref_template: String
}

// Per-target, per-method template contract. Sibling carrier to §6a
// `MethodContract` in `src/v3/std/algebra.dag` — that one carries
// target-agnostic cost/complexity metadata keyed by
Expand Down Expand Up @@ -378,7 +401,7 @@ type PlaceholderConvention
type MethodTemplateContract {
dag_method: MethodRef
runtime_template: String
emit_template: String
emit_template: MethodEmitTemplate
wraps_result: Bool
placeholder_convention: PlaceholderConvention
}
Loading
Loading