Repository navigation
feat(v3): add MethodTemplateContract substrate carrier #1175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bd8725b
2fd9eed
7994d36
fd494f6
ef14405
57bbd40
d92b3e0
186c0f8
4602659
208150a
660e2ab
51fb2b4
6496a0c
7f475e0
2c13dc8
61ad6ea
b18a5ec
b7e6988
66fa643
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| //! **Layer:** integration | ||
| //! | ||
| //! Acceptance for `MethodTemplateContract` substrate carrier in | ||
| //! `src/v3/std/emit_model.dag`. Sibling type to §6a `MethodContract` in | ||
| //! `src/v3/std/algebra.dag`; this PR lands the type only — row population | ||
| //! and `MethodTranslation` / `SimpleMethodSpec` retirement are | ||
| //! Grounding-owned follow-ups. | ||
| //! | ||
| //! Three claims per Director dispatch: | ||
| //! - `method_template_contract_distinct_from_method_contract` | ||
| //! - `method_template_contract_per_target_dag_method_unique` | ||
| //! (vacuous today over zero rows; load-bearing once Grounding populates) | ||
| //! - `method_template_contract_does_not_carry_cost_data` | ||
|
|
||
| use std::collections::HashSet; | ||
| use v3_compiler::dag::{Dag, DeclarationId, Field, TypeConnective}; | ||
| use v3_compiler::generated_full_bootstrap_dag; | ||
|
|
||
| fn conj_field_labels(dag: &Dag, name: &str) -> Vec<String> { | ||
| let decl = dag | ||
| .declaration_by_name(name) | ||
| .unwrap_or_else(|| panic!("`{name}` missing from full bootstrap")); | ||
| match &decl.connective { | ||
| TypeConnective::Conj { children } => children.iter().map(|f| f.label.clone()).collect(), | ||
| other => panic!("`{name}` is not a Conj: {other:?}"), | ||
| } | ||
| } | ||
|
|
||
| fn decl_id_by_name(dag: &Dag, name: &str) -> DeclarationId { | ||
| dag.declaration_by_name(name) | ||
| .unwrap_or_else(|| panic!("`{name}` missing from full bootstrap")) | ||
| .id | ||
| } | ||
|
|
||
| #[test] | ||
| fn method_template_contract_distinct_from_method_contract() { | ||
| let dag = generated_full_bootstrap_dag(); | ||
|
|
||
| let template_id = decl_id_by_name(&dag, "MethodTemplateContract"); | ||
| let metadata_id = decl_id_by_name(&dag, "MethodContract"); | ||
| assert_ne!( | ||
| template_id, metadata_id, | ||
| "MethodTemplateContract and §6a MethodContract must be distinct \ | ||
| declarations (P2 single-authority)" | ||
| ); | ||
|
|
||
| let template_fields: HashSet<String> = conj_field_labels(&dag, "MethodTemplateContract") | ||
| .into_iter() | ||
| .collect(); | ||
| let metadata_fields: HashSet<String> = conj_field_labels(&dag, "MethodContract") | ||
| .into_iter() | ||
| .collect(); | ||
| assert!( | ||
| template_fields.is_disjoint(&metadata_fields), | ||
| "MethodTemplateContract and §6a MethodContract field sets must be \ | ||
| disjoint — they are orthogonal sibling facts attached to method \ | ||
| declarations (P1 step 1). template={template_fields:?} \ | ||
| metadata={metadata_fields:?}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn method_template_contract_does_not_carry_cost_data() { | ||
| let dag = generated_full_bootstrap_dag(); | ||
| let labels: HashSet<String> = conj_field_labels(&dag, "MethodTemplateContract") | ||
| .into_iter() | ||
| .collect(); | ||
|
|
||
| for forbidden in ["cost_shape", "size_effect", "callback_element_position"] { | ||
| assert!( | ||
| !labels.contains(forbidden), | ||
| "MethodTemplateContract carries `{forbidden}` — that field \ | ||
| belongs on §6a MethodContract (target-agnostic cost/complexity \ | ||
| metadata). Template-contract carrier holds only render-template \ | ||
| facts (P1 step 2). actual fields={labels:?}" | ||
| ); | ||
| } | ||
|
|
||
| let expected: HashSet<&str> = [ | ||
| "dag_method", | ||
| "runtime_template", | ||
| "emit_template", | ||
| "wraps_result", | ||
| "placeholder_convention", | ||
| ] | ||
| .into_iter() | ||
| .collect(); | ||
| let actual: HashSet<&str> = labels.iter().map(String::as_str).collect(); | ||
| assert_eq!( | ||
| actual, expected, | ||
| "MethodTemplateContract field set diverged from Director-locked shape" | ||
| ); | ||
| } | ||
|
|
||
| /// Helper: extract `dag_method` decl ids from a list of MethodTemplateContract | ||
| /// rows represented as Conj field-binding lists. Used by the uniqueness check. | ||
| /// Today no such lists exist (Grounding owns row population); the check runs | ||
| /// vacuously over zero rows and becomes load-bearing once rows land. | ||
| fn assert_dag_method_unique(rows: &[Vec<Field>], list_name: &str) { | ||
| let mut seen: HashSet<DeclarationId> = HashSet::new(); | ||
| for row in rows { | ||
| let dag_method_field = row | ||
| .iter() | ||
| .find(|f| f.label == "dag_method") | ||
| .unwrap_or_else(|| panic!("row in `{list_name}` missing `dag_method` field")); | ||
| assert!( | ||
| seen.insert(dag_method_field.ty), | ||
| "duplicate `dag_method` in `{list_name}` — per-target \ | ||
| MethodTemplateContract rows must be unique by `dag_method`" | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn method_template_contract_per_target_dag_method_unique() { | ||
| // Substrate-only PR: zero `List<MethodTemplateContract>` data lists exist | ||
| // today (Grounding owns Rust/Python/Go row population). The uniqueness | ||
| // check is wired here and runs vacuously over zero synthetic rows. | ||
| let zero_rows: Vec<Vec<Field>> = Vec::new(); | ||
| for list_name in EXPECTED_PER_TARGET_LISTS { | ||
| assert_dag_method_unique(&zero_rows, list_name); | ||
| } | ||
|
|
||
| // Fail-loud trigger: when Grounding lands ANY of the per-target row lists, | ||
| // this assertion fires and forces this test to grow real row-walking | ||
| // logic in lock-step. Without it, the vacuous pass above could silently | ||
| // outlive its trigger and leave per-target uniqueness unchecked once rows | ||
| // exist. The list authority is `data <target>_method_template_contracts` | ||
| // in the per-target extdeps file (Grounding-owned). | ||
| let dag = generated_full_bootstrap_dag(); | ||
| let landed: Vec<&str> = EXPECTED_PER_TARGET_LISTS | ||
| .iter() | ||
| .copied() | ||
| .filter(|name| dag.declaration_by_name(name).is_some()) | ||
| .collect(); | ||
| assert!( | ||
| landed.is_empty(), | ||
| "per-target `MethodTemplateContract` row list(s) have landed: {landed:?}. \ | ||
| Grow this test to enumerate each list's rows and hand them to \ | ||
| `assert_dag_method_unique` instead of relying on the synthetic \ | ||
| zero-row vacuous pass above." | ||
| ); | ||
| } | ||
|
|
||
| const EXPECTED_PER_TARGET_LISTS: &[&str] = &[ | ||
| "rust_method_template_contracts", | ||
| "python_method_template_contracts", | ||
| "go_method_template_contracts", | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -315,3 +315,62 @@ type LanguageSpec { | |
| collection_ops: DeclarationRef | ||
| values: DeclarationRef | ||
| } | ||
|
|
||
| // Placeholder convention for method-template render strings. | ||
| // | ||
| // `IndexedArgs` — positional placeholders `{arg0}`, `{arg1}`, … as used by | ||
| // `dsl/extdeps/languages/{rust,python,go}/runtime.dag` `MethodTranslation` | ||
| // rows. | ||
| // `NamedArg` — single `{arg}` placeholder as used by Rust `emit.dag` | ||
| // `SimpleMethodSpec` and Python/Go `emit.dag` template maps. | ||
| // | ||
| // The receiver placeholder `{recv}` is common to both conventions and lives | ||
| // outside this axis. | ||
| // | ||
| // 🟡 SCAFFOLD coproduct. Pattern 1 (per-call template typing) fails because | ||
| // today's templates are opaque `String` values whose placeholder dialect is | ||
| // observable only by string inspection — there is no upstream type-system | ||
| // fact distinguishing `{arg0}/{arg1}` indexed slots from a single named | ||
| // `{arg}` slot. Pattern 2 (collapse to a single labeled record) erases the | ||
| // closed semantic split that consumers actually dispatch on. Pattern 3 is | ||
| // the dissolution path: when method-template substrate parses templates | ||
| // into structured slot facts (a typed `List<TemplateSegment>` with arity | ||
| // and slot-kind known structurally), this convention tag derives from | ||
| // segment shape and the named coproduct retires. Trigger: structured | ||
| // template-segment substrate in a follow-up substrate-completion lane. | ||
| // Until then this scaffold is the single declared authority for the | ||
| // dialect axis. | ||
| type PlaceholderConvention | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified at current head — 🟡 SCAFFOLD coproduct receipt is present at |
||
| = IndexedArgs | ||
| | NamedArg | ||
|
|
||
| // 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 | ||
| // `(algebra_id, method_id)`; this one carries target-specific render | ||
| // templates keyed by `dag_method`. The two are orthogonal sibling facts | ||
| // attached to method declarations; neither is a parent or child of the | ||
| // other (P1 step 1). | ||
| // | ||
| // Single template-contract fact per row — no cost/complexity fields | ||
| // (P1 step 2). Per-target identity is carried by the containing | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BLOCKING: Per-target identity is only carried by the containing list convention, so the target fact does not flow as a typed substrate fact when rows are consumed downstream.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Director-locked on parent inbox #1130 before this PR opened. Per-target identity carried by the containing |
||
| // `data <target>_method_template_contracts: List<MethodTemplateContract>` | ||
| // list authority, not duplicated on the row (P2 single-authority). | ||
| // | ||
| // `dag_method: DeclarationRef` aligns with the in-file convention | ||
| // (`op: DeclarationRef` on `OperatorRealization`, `language` / `target` | ||
| // on every realization). When a method-declaration registry lands in | ||
| // `dsl/std/`, this field can refine to a typed `MethodRef` without | ||
| // changing consumers. | ||
| // | ||
| // 🟡 TRANSITIONAL. This carrier consolidates the parallel-rep ×3 between | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BLOCKING: The new carrier is explicitly transitional and defers row population plus old-carrier retirement to follow-up work, so this PR adds another substrate shape without reducing the parallel MethodTranslation/SimpleMethodSpec/template-map authorities.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manager-accepted scope split (latest manager triage on head 57bbd40, third paragraph: "Representation split from data migration — accepted as a manager split. This PR is intentionally the Substrate-owned type slice. Grounding owns rows and |
||
| // `MethodTranslation` (`runtime.dag`) and `SimpleMethodSpec` (`emit.dag`) | ||
| // across Rust/Python/Go. Row population and old-carrier retirement are | ||
| // Grounding-owned and land in follow-up work. | ||
| type MethodTemplateContract { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BLOCKING: MethodTemplateContract makes runtime_template and emit_template mandatory in one row even though the existing runtime and emit method sets and placeholder conventions differ, forcing fabricated fields or dropped facts instead of modeling the real contracts faithfully.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Director-locked on parent inbox #1130: the flat two-template shape is the accepted carrier for this PR; the dissolved |
||
| dag_method: DeclarationRef | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BLOCKING:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Director-locked Q1 answer on parent inbox #1130 (verbatim): "Use (a) The illegal-state concern is real but is the same shape as the existing |
||
| runtime_template: String | ||
| emit_template: String | ||
| wraps_result: Bool | ||
| placeholder_convention: PlaceholderConvention | ||
| } | ||
This comment was marked as resolved.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in head —
PlaceholderConventionnow carries a 🟡 SCAFFOLD coproduct receipt with named dissolution trigger (structured template-segment substrate replacing dialect-by-string-inspection). — sent from tidy-wolf-507