diff --git a/dag/gunbc/recurring_failure_mode/collection_callback_operation_unrealized_in_v2_lowering.dag b/dag/gunbc/recurring_failure_mode/collection_callback_operation_unrealized_in_v2_lowering.dag new file mode 100644 index 00000000000..8898db7a72f --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/collection_callback_operation_unrealized_in_v2_lowering.dag @@ -0,0 +1,24 @@ +module gunbc.recurring_failure_mode.collection_callback_operation_unrealized_in_v2_lowering + +import std.types { NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data collection_callback_operation_unrealized_in_v2_lowering: RecurringFailureMode = RecurringFailureMode { + identity: "collection_callback_operation_unrealized_in_v2_lowering" as NonEmptyStr, + receipts: [ + "INVALID STATE: std.algebra models collection callback operations as template rows with callback_element_position Present (map, filter, flat_map, any, all, sort_by), but v2 body lowering realized only the fold family. A call of an unrealized row reaches v2.compiler.resolve as a bare name and refuses resolve_reason_unbound_symbol. HARM: v2.std.node calls `map(node.children, e => Edge { .. })`, so on the native route it refused its own resolve and dropped out of the closure index; about twenty other v2 modules call bare `map(`. The refusal is typed and located, so this is a capability gap on the loud side of the ladder, never a silent wrong answer.", + "DISTINGUISHING FACTS: the earliest unjustified boundary was the MODEL, not lowering. A realized roster call binds its head to the row's declaration (v2.compiler.resolve resolve_realized_declaration), and map / flat_map / any / all / sort_by were inline list elements in std.algebra free_monoid_collection_templates and finite_power_set_templates, copied once per list, with no declaration to bind to. They are now named shapes (collection_map_shape and siblings) referenced from both lists, as fold and filter already were.", + "CLIMB (N7-1b): map and all are realized through the one fold encoding, keyed on their rows in v2.compiler.fold_lowering collection_roster_rows. Each step is synthesized around the callback's own binder (CallbackRealization), and is O(1) per member: map conses one cell onto the carrier, and all joins one Bool. Controls: v2.test.claim.compiler.collection_callback_realization. all's body types through &&, which infer does not yet join (a separate frontier), so its control asserts the head binding and lowering, not infer acceptance.", + "COUNTED FRONTIER, 4 rows, by name. filter and any: v2.std.algebra DECLARES ordinary fns `filter` and `any`, and 41 modules each import and call them positionally or piped (measured at N7-1b's base, 2dae714bf4). Lowering classifies a head by its spelling without a binding, so a realized row would lower those calls to a Loop that v2.compiler.resolve resolve_realized_declaration refuses as realized_declaration_shadowed. That is a section 3 fork, the roster row and the corpus fn being one operation under two authorities. TRIGGER: the fork is consolidated (v2.std.algebra any/filter retired onto the roster operation, or a binding-aware realization under which a head bound to a corpus declaration stays an ordinary call), SUFFICIENT FOR an unbound `filter(xs, e => ..)` / `any(xs, e => ..)` to lower and bind its row while every imported call keeps binding. Its guard is ccr_declared_any_stays_an_ordinary_call. flat_map: its step is itself a fold over the callback's result (v2.std.algebra list_flat_map's nested fold_list_right), which the synthesized-step table cannot express as one cell. TRIGGER: a nested-fold step, i.e. a CallbackRealization arm whose step body is a fold encoding over the callback body with the outer accumulator as its init, SUFFICIENT FOR `flat_map(xs, e => f(e))` to lower and its head to bind collection_flat_map_shape. sort_by: cost_shape ShapeSortBody, which is not an iterate body, so it has no fold realization at all. TRIGGER: a sort realization the template's ShapeSortBody names, SUFFICIENT FOR `sort_by(xs, e => k(e))` to lower and its head to bind collection_sort_by_shape. RUNG: mitigatable (typed refusal at resolve). CEILING: structurally guaranteed: every fact is decidable from the row.", + ], + evidence: [ + DeclarationRef { module_path: "std.algebra", decl_name: "collection_map_shape", field: WholeDeclaration }, + DeclarationRef { module_path: "std.algebra", decl_name: "collection_flat_map_shape", field: WholeDeclaration }, + DeclarationRef { module_path: "std.algebra", decl_name: "collection_sort_by_shape", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "collection_roster_rows", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "CallbackRealization", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.body_lowering_fold", decl_name: "body_lower_callback_encoding", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.test.claim.compiler.collection_callback_realization", decl_name: "ccr_map_over_records_reads_a_field", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/fold_realized_loop_carries_no_iteration_direction.dag b/dag/gunbc/recurring_failure_mode/fold_realized_loop_carries_no_iteration_direction.dag new file mode 100644 index 00000000000..3306a6c1ec8 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/fold_realized_loop_carries_no_iteration_direction.dag @@ -0,0 +1,26 @@ +module gunbc.recurring_failure_mode.fold_realized_loop_carries_no_iteration_direction + +import std.types { NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data fold_realized_loop_carries_no_iteration_direction: RecurringFailureMode = RecurringFailureMode { + identity: "fold_realized_loop_carries_no_iteration_direction" as NonEmptyStr, + receipts: [ + "INVALID STATE: a fold-realized Loop's encoding does not show its iteration direction. v2.compiler.fold_lowering fold_recurrence_encoding builds the identical Loop for the collection fold, fold_list (left), fold_list_right (right) and, since N7-1b, map, whose synthesized step conses onto the carrier and is order-preserving ONLY under right-to-left iteration (all joins with &&, which is order-insensitive). An executor that ran a map encoding left to right would return its results reversed: silent wrongness. CLIMBED IN gunbc#13069 (review 74982 asked that the PR introducing map not ship the reversible encoding with only this row): the direction is now a DECLARED FACT OF THE REALIZATION, v2.compiler.fold_lowering FoldIterationDirection, owned by the step synthesis that needs it (callback_realization_iteration_direction: map right to left) and by the authored forms (an authored fold and fold_list left, fold_list_right right), and derived for any Loop from the realized declaration it already carries (^loop_realized_declaration_edge) by fold_realized_iteration_direction. fold_list and fold_list_right are therefore no longer indistinguishable, and a path that names no collection fold derives no direction rather than a default.", + "WHAT REMAINS: no executor of a fold-realized Loop exists to consume the fact. Both installed Loop handlers REFUSE: v2.extdeps.runtimes.v2_evaluator v2_eval_step_loop and v2.program program_step_loop each return a rejected outcome, so v2.compiler.eval's eval_loop_node reaches a typed refusal, never a value, and the native route executes the seed's emission of the .dag source, not this encoding. So the residual is fail-closed, not latent silent wrongness. The obligation it leaves is on the executor that replaces those refusals: it must read fold_realized_iteration_direction, not assume an order.", + "DISTINGUISHING FACTS: this is not a cost defect. Each synthesized step is O(1) per member (one Cons onto the carrier, never snoc or append), held structurally by v2.test.claim.compiler.collection_callback_realization ccr_map_step_conses_onto_its_accumulator; a snoc step would make map left to right at quadratic cost, which is why the order is declared rather than designed away.", + "RUNG: the fact is structurally carried and its derivation is enforced by discriminating controls (ccr_map_loop_iterates_right_to_left on the real map route; ccr_fold_list_and_fold_list_right_are_told_apart; both red when map or fold_list_right is declared left to right, measured). CEILING: structurally impossible once execution exists. NEXT-RUNG TRIGGER, stated as the capability: an executor of fold-realized Loops replacing the refusing step_loop handlers that consumes fold_realized_iteration_direction, SUFFICIENT FOR map, fold_list and fold_list_right encodings to evaluate in their declared order, with an executing control that reverses the declared direction and goes red.", + ], + evidence: [ + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "fold_recurrence_encoding", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "CallbackRealization", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "FoldIterationDirection", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.compiler.fold_lowering", decl_name: "fold_realized_iteration_direction", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.extdeps.runtimes.v2_evaluator", decl_name: "v2_eval_step_loop", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.program", decl_name: "program_step_loop", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.test.claim.compiler.collection_callback_realization", decl_name: "ccr_map_loop_iterates_right_to_left", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.test.claim.compiler.collection_callback_realization", decl_name: "ccr_fold_list_and_fold_list_right_are_told_apart", field: WholeDeclaration }, + DeclarationRef { module_path: "v2.test.claim.compiler.collection_callback_realization", decl_name: "ccr_map_step_conses_onto_its_accumulator", field: WholeDeclaration }, + ], +} diff --git a/dag/std/algebra.dag b/dag/std/algebra.dag index b7d6ddc2b0a..444c2656349 100644 --- a/dag/std/algebra.dag +++ b/dag/std/algebra.dag @@ -394,6 +394,31 @@ fn collection_fold_shape() -> AlgebraFieldTemplate { { name: "fold", param_types: [ReceiverSelf, AlgebraTypeVariable { id: "FoldAccumulator" }, CallableOf { params: [AlgebraTypeVariable { id: "FoldAccumulator" }, ReceiverElement], return_type: AlgebraTypeVariable { id: "FoldAccumulator" } }], return_type: AlgebraTypeVariable { id: "FoldAccumulator" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 1 } } } +// The collection callback operations, each ONE template declared once and referenced from every profile +// list that carries it, as filter and fold are. A named row is what a realized call head binds to +// (v2.compiler.fold_lowering fold_family_realization), and one row per operation removes the copy each +// list used to hold. + +fn collection_map_shape() -> AlgebraFieldTemplate { + { name: "map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: AlgebraTypeVariable { id: "MappedElement" } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } } +} + +fn collection_flat_map_shape() -> AlgebraFieldTemplate { + { name: "flat_map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } } +} + +fn collection_any_shape() -> AlgebraFieldTemplate { + { name: "any", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } } +} + +fn collection_all_shape() -> AlgebraFieldTemplate { + { name: "all", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } } +} + +fn collection_sort_by_shape() -> AlgebraFieldTemplate { + { name: "sort_by", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: AlgebraTypeVariable { id: "SortKey" } }], return_type: ReceiverSelf, size_effect: Present { value: IdentityEffect }, cost_shape: Present { value: ShapeSortBody }, callback_element_position: Present { value: 0 } } +} + type StepPositionScan { next: Int found: Int? @@ -958,11 +983,11 @@ fn finite_power_set_templates() -> List { { name: "member", param_types: [ReceiverSelf, ReceiverElement], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: none, callback_element_position: none }, { name: "contains", param_types: [ReceiverSelf, ReceiverElement], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: none, callback_element_position: none }, collection_filter_shape(), - { name: "map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: AlgebraTypeVariable { id: "MappedElement" } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, - { name: "flat_map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, + collection_map_shape(), + collection_flat_map_shape(), collection_fold_shape(), - { name: "any", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, - { name: "all", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, + collection_any_shape(), + collection_all_shape(), { name: "count", param_types: [ReceiverSelf], return_type: NamedTemplate { name: "std.nat.Nat" }, size_effect: none, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none }, { name: "length", param_types: [ReceiverSelf], return_type: NamedTemplate { name: "std.nat.Nat" }, size_effect: none, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none } ] @@ -1002,19 +1027,19 @@ fn free_monoid_scalar_templates() -> List { fn free_monoid_collection_templates() -> List { [ - { name: "map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: AlgebraTypeVariable { id: "MappedElement" } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, + collection_map_shape(), collection_filter_shape(), - { name: "flat_map", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } } }], return_type: ContainerOf { source: SameAsReceiver, element: AlgebraTypeVariable { id: "MappedElement" } }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, + collection_flat_map_shape(), collection_fold_shape(), - { name: "any", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, - { name: "all", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: NamedTemplate { name: "Bool" } }], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: Present { value: 0 } }, + collection_any_shape(), + collection_all_shape(), { name: "count", param_types: [ReceiverSelf], return_type: NamedTemplate { name: "std.nat.Nat" }, size_effect: none, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none }, { name: "first", param_types: [ReceiverSelf], return_type: OptionalOf { inner: ReceiverElement }, size_effect: Present { value: ProjectionEffect }, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none }, { name: "last", param_types: [ReceiverSelf], return_type: OptionalOf { inner: ReceiverElement }, size_effect: Present { value: ProjectionEffect }, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none }, { name: "get", param_types: [ReceiverSelf, NamedTemplate { name: "Int" }], return_type: OptionalOf { inner: ReceiverElement }, size_effect: Present { value: ProjectionEffect }, cost_shape: Present { value: ShapeLinearScan }, callback_element_position: none }, { name: "skip", param_types: [ReceiverSelf, NamedTemplate { name: "Int" }], return_type: ReceiverSelf, size_effect: Present { value: ShrinkEffect }, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: none }, { name: "take", param_types: [ReceiverSelf, NamedTemplate { name: "Int" }], return_type: ReceiverSelf, size_effect: none, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: none }, - { name: "sort_by", param_types: [ReceiverSelf, CallableOf { params: [ReceiverElement], return_type: AlgebraTypeVariable { id: "SortKey" } }], return_type: ReceiverSelf, size_effect: Present { value: IdentityEffect }, cost_shape: Present { value: ShapeSortBody }, callback_element_position: Present { value: 0 } }, + collection_sort_by_shape(), { name: "append", param_types: [ReceiverSelf, ReceiverElement], return_type: ReceiverSelf, size_effect: none, cost_shape: Present { value: ShapeConstant }, callback_element_position: none }, { name: "contains", param_types: [ReceiverSelf, ReceiverElement], return_type: NamedTemplate { name: "Bool" }, size_effect: none, cost_shape: none, callback_element_position: none }, { name: "enumerate", param_types: [ReceiverSelf], return_type: ContainerOf { source: SameAsReceiver, element: TupleOf { first: NamedTemplate { name: "Int" }, second: ReceiverElement } }, size_effect: Present { value: IdentityEffect }, cost_shape: Present { value: ShapeIterateBody }, callback_element_position: none }, diff --git a/src/v1/stage0/src/std_algebra.rs b/src/v1/stage0/src/std_algebra.rs index c88e2a7f856..8c99825b7fe 100644 --- a/src/v1/stage0/src/std_algebra.rs +++ b/src/v1/stage0/src/std_algebra.rs @@ -517,6 +517,118 @@ pub fn collection_fold_shape() -> Rc { }) } +pub fn collection_map_shape() -> Rc { + Rc::new(AlgebraFieldTemplate { + name: "map".to_string(), + param_types: Rc::new(vec![ + Rc::new(AlgebraTypeTemplate::ReceiverSelf), + Rc::new(AlgebraTypeTemplate::CallableOf { + params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), + return_type: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { + id: "MappedElement".to_string(), + }), + }), + ]), + return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { + source: Rc::new(ContainerSource::SameAsReceiver), + element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { + id: "MappedElement".to_string(), + }), + }), + size_effect: std::option::Option::None, + cost_shape: Some(CostShape::ShapeIterateBody), + callback_element_position: Some(0), + }) +} + +pub fn collection_flat_map_shape() -> Rc { + Rc::new(AlgebraFieldTemplate { + name: "flat_map".to_string(), + param_types: Rc::new(vec![ + Rc::new(AlgebraTypeTemplate::ReceiverSelf), + Rc::new(AlgebraTypeTemplate::CallableOf { + params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), + return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { + source: Rc::new(ContainerSource::SameAsReceiver), + element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { + id: "MappedElement".to_string(), + }), + }), + }), + ]), + return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { + source: Rc::new(ContainerSource::SameAsReceiver), + element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { + id: "MappedElement".to_string(), + }), + }), + size_effect: std::option::Option::None, + cost_shape: Some(CostShape::ShapeIterateBody), + callback_element_position: Some(0), + }) +} + +pub fn collection_any_shape() -> Rc { + Rc::new(AlgebraFieldTemplate { + name: "any".to_string(), + param_types: Rc::new(vec![ + Rc::new(AlgebraTypeTemplate::ReceiverSelf), + Rc::new(AlgebraTypeTemplate::CallableOf { + params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), + return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { + name: "Bool".to_string(), + }), + }), + ]), + return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { + name: "Bool".to_string(), + }), + size_effect: std::option::Option::None, + cost_shape: Some(CostShape::ShapeIterateBody), + callback_element_position: Some(0), + }) +} + +pub fn collection_all_shape() -> Rc { + Rc::new(AlgebraFieldTemplate { + name: "all".to_string(), + param_types: Rc::new(vec![ + Rc::new(AlgebraTypeTemplate::ReceiverSelf), + Rc::new(AlgebraTypeTemplate::CallableOf { + params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), + return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { + name: "Bool".to_string(), + }), + }), + ]), + return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { + name: "Bool".to_string(), + }), + size_effect: std::option::Option::None, + cost_shape: Some(CostShape::ShapeIterateBody), + callback_element_position: Some(0), + }) +} + +pub fn collection_sort_by_shape() -> Rc { + Rc::new(AlgebraFieldTemplate { + name: "sort_by".to_string(), + param_types: Rc::new(vec![ + Rc::new(AlgebraTypeTemplate::ReceiverSelf), + Rc::new(AlgebraTypeTemplate::CallableOf { + params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), + return_type: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { + id: "SortKey".to_string(), + }), + }), + ]), + return_type: Rc::new(AlgebraTypeTemplate::ReceiverSelf), + size_effect: Some(CollectionSizeEffect::IdentityEffect), + cost_shape: Some(CostShape::ShapeSortBody), + callback_element_position: Some(0), + }) +} + #[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] pub struct StepPositionScan { pub next: i64, @@ -1447,88 +1559,11 @@ pub fn finite_power_set_templates() -> Rc>> { callback_element_position: std::option::Option::None, }), collection_filter_shape(), - Rc::new(AlgebraFieldTemplate { - name: "map".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), - Rc::new(AlgebraFieldTemplate { - name: "flat_map".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), + collection_map_shape(), + collection_flat_map_shape(), collection_fold_shape(), - Rc::new(AlgebraFieldTemplate { - name: "any".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), - Rc::new(AlgebraFieldTemplate { - name: "all".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), + collection_any_shape(), + collection_all_shape(), Rc::new(AlgebraFieldTemplate { name: "count".to_string(), param_types: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverSelf)]), @@ -1802,89 +1837,12 @@ pub fn free_monoid_scalar_templates() -> Rc>> { pub fn free_monoid_collection_templates() -> Rc>> { Rc::new(vec![ - Rc::new(AlgebraFieldTemplate { - name: "map".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), + collection_map_shape(), collection_filter_shape(), - Rc::new(AlgebraFieldTemplate { - name: "flat_map".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::ContainerOf { - source: Rc::new(ContainerSource::SameAsReceiver), - element: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "MappedElement".to_string(), - }), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), + collection_flat_map_shape(), collection_fold_shape(), - Rc::new(AlgebraFieldTemplate { - name: "any".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), - Rc::new(AlgebraFieldTemplate { - name: "all".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::NamedTemplate { - name: "Bool".to_string(), - }), - size_effect: std::option::Option::None, - cost_shape: Some(CostShape::ShapeIterateBody), - callback_element_position: Some(0), - }), + collection_any_shape(), + collection_all_shape(), Rc::new(AlgebraFieldTemplate { name: "count".to_string(), param_types: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverSelf)]), @@ -1956,22 +1914,7 @@ pub fn free_monoid_collection_templates() -> Rc>> { cost_shape: Some(CostShape::ShapeIterateBody), callback_element_position: std::option::Option::None, }), - Rc::new(AlgebraFieldTemplate { - name: "sort_by".to_string(), - param_types: Rc::new(vec![ - Rc::new(AlgebraTypeTemplate::ReceiverSelf), - Rc::new(AlgebraTypeTemplate::CallableOf { - params: Rc::new(vec![Rc::new(AlgebraTypeTemplate::ReceiverElement)]), - return_type: Rc::new(AlgebraTypeTemplate::AlgebraTypeVariable { - id: "SortKey".to_string(), - }), - }), - ]), - return_type: Rc::new(AlgebraTypeTemplate::ReceiverSelf), - size_effect: Some(CollectionSizeEffect::IdentityEffect), - cost_shape: Some(CostShape::ShapeSortBody), - callback_element_position: Some(0), - }), + collection_sort_by_shape(), Rc::new(AlgebraFieldTemplate { name: "append".to_string(), param_types: Rc::new(vec![ diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index 08f4adfaedd..fac9ede5b58 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -18,6 +18,11 @@ import std.dissolution { DissolutionCondition, dissolution_description, unbound_ import std.algebra { Cons, Empty, FreeMonoid, list_append, list_snoc_item } import v2.compiler.fold_lowering { + CallbackAllByAnd, + CallbackCallOperands, + CallbackMapsToCons, + CallbackRealization, + FoldCallCallbackLowered, FoldCallLowered, FunctionValueBinder, FunctionValueKind, @@ -73,7 +78,8 @@ import v2.extdeps.languages.dag { dag_surface_edge_label, dag_surface_edge_is, } -import v2.std.anonymous_binder { DeclaredParameterList, anonymous_binder_mint_parameter_list, fresh_return_type_variable, fresh_type_variable } +import v2.std.anonymous_binder { DeclaredParameterList, anonymous_binder_mint_parameter_list, fresh_fold_accumulator, fresh_return_type_variable, fresh_type_variable } +import v2.std.node { content_hash } import v2.std.algebra { fold_list, list_map, length, zip_eq } import v2.std.collection { empty_map, @@ -1983,6 +1989,8 @@ fn body_lower_piped_fold_optional(acc: Node, operator: Node, right: Node) -> Opt match fold_call_lowering_piped(fold_call: call, receiver: acc) { FoldCallLowered { operands: operands } => optional_present(value: body_lower_fold_encoding_from(operands: operands, collection: outcome_accepted(value: acc))) + FoldCallCallbackLowered { operands: operands } => + optional_present(value: body_lower_callback_encoding_from(operands: operands, collection: outcome_accepted(value: acc))) FoldCallStepShapeInvalid { diagnostic: d } => optional_present(value: outcome_rejected(d: d)) FoldCallOperandAbsent { diagnostic: d } => optional_present(value: outcome_rejected(d: d)) FoldCallStepFormUnresolved { diagnostic: _ } => optional_absent() @@ -2221,6 +2229,12 @@ fn body_lower_try_fold_loop(node: Node) -> Outcome> { Accepted { value: encoding, diagnostics: d } => outcome_with_diagnostics(value: optional_present(value: encoding), diagnostics: d) } + FoldCallCallbackLowered { operands: operands } => + match body_lower_callback_encoding(operands: operands) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: encoding, diagnostics: d } => + outcome_with_diagnostics(value: optional_present(value: encoding), diagnostics: d) + } FoldCallOperandAbsent { diagnostic: d } => outcome_rejected(d: d) FoldCallStepFormUnresolved { diagnostic: d } => outcome_with_diagnostics( @@ -3474,6 +3488,130 @@ fn body_lower_fold_encoding_from(operands: FoldCallOperands, collection: Outcome }) } +// A COLLECTION CALLBACK OPERATION LOWERS TO THE SAME FOLD ENCODING, its step synthesized around the +// callback (v2.compiler.fold_lowering CallbackRealization names each operation's step and init). The +// callback is read by the one function-value reader, its body lowered by the one body reader, and its +// AUTHORED binder becomes the synthesized step's member formal (actual 1), so v2.compiler.infer types it +// from the domain element by role and nothing applies a lambda value. The accumulator formal (actual 0) is +// minted by v2.std.anonymous_binder fresh_fold_accumulator. The Arrow is built by the one function-value +// Arrow producer, so the step is an ordinary callable with fresh type variables like any other. +// +// The constructors the step and init name (Cons, Empty) are referenced by their declaring path, +// std.algebra, never by a bare spelling the calling module might bind differently or not at all. +// +// OCCURRENCES. The init stands for no authored text, so it is synthetic. The step's nodes are members of +// the callback's IMAGE (v2.std.node node_lowered_from): the step Arrow closes that image, so each member +// becomes pending and normalize allocates it its own projected occurrence, which is the key a lexical +// reference to the accumulator or the member is recorded by (v2.compiler.resolve +// resolve_lexical_reference). The callback's own body keeps its authored occurrences. +// +// AN AUTHORED CALLBACK RETURN TYPE IS NOT READ HERE and refuses as shape_unread: the synthesized step's +// return is the carrier, not the callback's, so carrying the annotation would put it on the wrong +// position, and dropping it would discard an authored fact. +fn body_lower_callback_encoding(operands: CallbackCallOperands) -> Outcome { + body_lower_callback_encoding_from(operands: operands, collection: body_lower_value_read(value: operands.collection)) +} + +fn body_lower_algebra_constructor_reference(name: Symbol, source: Node) -> Node { + body_lower_qualified_name_spine_node( + segments: [ + node_lowered_from(kind: TypeNode { connective: Atom { identity: ^std } }, children: [], source: source), + node_lowered_from(kind: TypeNode { connective: Atom { identity: ^algebra } }, children: [], source: source), + node_lowered_from(kind: TypeNode { connective: Atom { identity: name } }, children: [], source: source) + ], + source: source + ) +} + +fn body_lower_callback_cons(head: Node, tail: Node, source: Node) -> Node { + close_lowered_image(root: construct_node( + reference: body_lower_algebra_constructor_reference(name: ^Cons, source: source), + field_edges: [Edge { label: Authored { name: ^head }, target: head }, Edge { label: Authored { name: ^tail }, target: tail }], + source: source + )) +} + +fn body_lower_callback_operator(token: Symbol) -> Node { + node_synthetic(kind: TypeNode { connective: Atom { identity: token } }, children: []) +} + +fn body_lower_callback_init(realization: CallbackRealization, source: Node) -> Node { + match realization { + CallbackMapsToCons => body_lower_algebra_constructor_reference(name: ^Empty, source: source) + CallbackAllByAnd => body_lower_param_ref_atom(source: source, identity: ^dag_token_kw_true) + } +} + +fn body_lower_callback_step_body(realization: CallbackRealization, accumulator: Symbol, body: Node, source: Node) -> Node { + let acc_ref = body_lower_param_ref_atom(source: source, identity: accumulator) + match realization { + CallbackMapsToCons => body_lower_callback_cons(head: body, tail: acc_ref, source: source) + CallbackAllByAnd => lower_binary_infix(left: acc_ref, operator: body_lower_callback_operator(token: ^dag_token_and_and), right: body) + } +} + +fn body_lower_callback_encoding_from(operands: CallbackCallOperands, collection: Outcome) -> Outcome { + let callback_node = operands.callback + let synthetic = node_synthetic(kind: TypeNode { connective: Conj }, children: []) + let unread: Outcome = outcome_rejected( + d: body_lower_diagnostic(reason: ^body_lowering_reason_function_value_shape_unread, n: callback_node) + ) + bind_outcome(o: collection, f: fn(collection) { + match body_lower_function_value_captured_optional(node: callback_node) { + Absent => unread + Present { value: capture } => + match function_value_parts_optional(captured: capture.captured, kind: capture.kind) { + Absent => unread + Present { value: parts } => + match parts.binders { + Cons { head: member, tail: Empty } => + match body_lower_function_value_codomain_optional(ret: parts.return_clause) { + Present { value: CodomainElided } => + match body_lower_function_value_body(body: parts.body) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: Absent, diagnostics: _ } => + outcome_rejected( + d: body_lower_diagnostic( + reason: ^body_lowering_reason_function_value_body_unread, + n: body_lower_function_value_body_node(body: parts.body) + ) + ) + Accepted { value: Present { value: body }, diagnostics: d } => + let accumulator = fresh_fold_accumulator(callback_digest: content_hash(n: callback_node).digest as String) + let accumulator_binder = FunctionValueBinder { + name: accumulator, + at: node_synthetic(kind: TypeNode { connective: Atom { identity: accumulator } }, children: []) + } + match body_lower_function_value_arrow( + shell: callback_node, + binders: [accumulator_binder, member], + codomain: CodomainElided, + body: body_lower_callback_step_body(realization: operands.realization, accumulator: accumulator, body: body, source: callback_node) + ) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: step, diagnostics: ad } => + outcome_with_diagnostics( + value: fold_recurrence_encoding(operands: FoldCallOperands { + head: operands.head, + collection: collection, + init: body_lower_callback_init(realization: operands.realization, source: synthetic), + step: step, + carrier: accumulator + }), + diagnostics: diagnostics_merge(outer: d, inner: ad) + ) + } + } + Present { value: CodomainAuthored { type_node: _ } } => unread + Absent => unread + } + _ => unread + } + } + } + }) +} + // A fold-family call, read off the node's own captured sequence (fold_lowering fold_call_head_symbol // reads its head atom directly -- no search), lowered to the fold encoding while its step is raw. fn body_lower_fold_family_call_first(node: Node) -> Optional> { @@ -3487,6 +3625,7 @@ fn body_lower_fold_family_call_first(node: Node) -> Optional> { Present { value: _ } => match fold_call_lowering(fold_call: captured) { FoldCallLowered { operands: operands } => optional_present(value: body_lower_fold_encoding(operands: operands)) + FoldCallCallbackLowered { operands: operands } => optional_present(value: body_lower_callback_encoding(operands: operands)) FoldCallStepShapeInvalid { diagnostic: d } => optional_present(value: outcome_rejected(d: d)) FoldCallOperandAbsent { diagnostic: d } => optional_present(value: outcome_rejected(d: d)) FoldCallStepFormUnresolved { diagnostic: _ } => optional_absent() diff --git a/src/v2/compiler/fold_lowering.dag b/src/v2/compiler/fold_lowering.dag index bbc32eb8558..c2e8961372e 100644 --- a/src/v2/compiler/fold_lowering.dag +++ b/src/v2/compiler/fold_lowering.dag @@ -31,7 +31,9 @@ import std.algebra { AlgebraFieldTemplate, Cons, Empty, + collection_all_shape, collection_fold_shape, + collection_map_shape, template_accumulator_position, template_receiver_position, template_step_position @@ -165,7 +167,8 @@ fn fold_row_position(position: Int?) -> Optional { } } -// The collection operation's slots, positions read off its template row. +// A roster operation's slots, positions read off its template row. A callback row (map, all) has no accumulator parameter, so template_accumulator_position answers Absent and its init slot +// is absent: the template itself says there is no authored init. fn collection_fold_slots(t: AlgebraFieldTemplate) -> FoldSlots { FoldSlots { collection: FoldSlot { name: optional_present(value: ^xs), position: fold_row_position(position: template_receiver_position(t: t)) }, @@ -174,34 +177,124 @@ fn collection_fold_slots(t: AlgebraFieldTemplate) -> FoldSlots { } } -// The collection operation's head, interned ONCE from its template row's own `name` -- still the one -// spelling -- so classifying a head is a symbol comparison. fold_family_realization runs at every call -// site body lowering meets; building the template and comparing lexemes there was a per-call-site cost -// on the native route's context phase (gunbc#12550 census). +// THE COLLECTION CALLBACK OPERATIONS, REALIZED THROUGH THE ONE FOLD ENCODING. A callback row's callable +// takes ONE element (callback_element_position 0) and returns no accumulator, so the call carries no step +// the recurrence can use as written: the step is SYNTHESIZED around the callback's own binder and body, +// and the init is supplied, both by the operation the row names (map is the fold desugaring of +// v2.std.algebra list_map): +// map(xs, e => B) init Empty, step (acc, e) => Cons { head: B, tail: acc } +// all(xs, e => B) init true, step (acc, e) => acc && B +// Each step is O(1) per member: it conses one cell onto the carrier or joins one Bool into it, and never +// copies it (no snoc, no append), which is the template's ShapeIterateBody at linear cost (DESIGN +// section 6). +// +// THE STEP SHAPE PER OPERATION IS AUTHORED, ONE ARM PER ROW, AND THAT IS A DECLARED FRONTIER, exactly +// as the corpus declarations' slots above are: the template row carries the operation's signature and +// cost, not a body. Its trigger is a realization body readable off the row. +// +// WHAT IS NOT HERE, BY NAME: filter and any (v2.std.algebra DECLARES ordinary fns of those names, which +// dozens of modules import; lowering classifies a head by spelling without a binding, so realizing the +// row would lower those calls to a Loop that resolve then refuses as shadowed), flat_map (its step is +// itself a fold over the callback's result) and sort_by (ShapeSortBody, not an iterate body). Their rows +// are named in std.algebra and their calls stay unrealized; gunbc.recurring_failure_mode +// collection_callback_operation_unrealized_in_v2_lowering is the counted frontier with each trigger. +type CallbackRealization + = CallbackMapsToCons + | CallbackAllByAnd + +type FoldRosterForm + = RosterAuthoredStep + | RosterCallbackStep { realization: CallbackRealization } + +type CollectionRosterRow { + head: Symbol + template: AlgebraFieldTemplate + row: DeclarationRef + form: FoldRosterForm +} + +fn collection_roster_row(t: AlgebraFieldTemplate, decl_name: String, form: FoldRosterForm) -> CollectionRosterRow { + CollectionRosterRow { + head: symbol_intern_lexeme(lexeme: t.name), + template: t, + row: DeclarationRef { module_path: "std.algebra", decl_name: decl_name, field: WholeDeclaration }, + form: form + } +} + +// THE ONE ROSTER OF REALIZED COLLECTION OPERATIONS, each head interned ONCE from its row's own `name`, so +// classifying a head is a symbol comparison. fold_family_realization runs at every call site body +// lowering meets, and building templates and comparing lexemes there was a per-call-site cost on the +// native route's context phase (gunbc#12550 census). +data collection_roster_rows: List = [ + collection_roster_row(t: collection_fold_shape(), decl_name: "collection_fold_shape", form: RosterAuthoredStep), + collection_roster_row(t: collection_map_shape(), decl_name: "collection_map_shape", form: RosterCallbackStep { realization: CallbackMapsToCons }), + collection_roster_row(t: collection_all_shape(), decl_name: "collection_all_shape", form: RosterCallbackStep { realization: CallbackAllByAnd }) +] + data collection_fold_head: Symbol = symbol_intern_lexeme(lexeme: collection_fold_shape().name) +fn collection_roster_row_optional(head: Symbol) -> Optional { + fold_list(xs: collection_roster_rows, empty: optional_absent(), cons: fn(acc, r) { + match acc { + Present { value: _ } => acc + Absent => if r.head == head { optional_present(value: r) } else { acc } + } + }) +} + +// Whether a roster operation's step is the authored recurrence step or one synthesized around a callback. +fn fold_roster_form(row: DeclarationRef) -> FoldRosterForm { + fold_list(xs: collection_roster_rows, empty: RosterAuthoredStep, cons: fn(acc, r) { + if r.row.decl_name == row.decl_name && r.row.module_path == row.module_path { r.form } else { acc } + }) +} + fn fold_family_realization(head: Symbol) -> Optional { - if head == collection_fold_head { - let t = collection_fold_shape() - optional_present(value: FoldRealizesRosterOperation { - template: t, - row: DeclarationRef { module_path: "std.algebra", decl_name: "collection_fold_shape", field: WholeDeclaration }, - slots: collection_fold_slots(t: t) - }) - } else if head == ^fold_list { - fold_realizes_declaration(module_path: "v2.std.algebra", decl_name: "fold_list", slots: FoldSlots { - collection: fold_declared_slot(name: ^xs, position: 0), init: fold_declared_slot(name: ^empty, position: 1), step: fold_declared_slot(name: ^cons, position: 2) - }) - } else if head == ^fold_list_right { - fold_realizes_declaration(module_path: "v2.std.algebra", decl_name: "fold_list_right", slots: FoldSlots { - collection: fold_declared_slot(name: ^xs, position: 0), init: fold_declared_slot(name: ^empty, position: 1), step: fold_declared_slot(name: ^snoc, position: 2) - }) - } else if head == ^fold_node { - fold_realizes_declaration(module_path: "v2.std.node", decl_name: "fold_node", slots: FoldSlots { - collection: fold_declared_slot(name: ^n, position: 0), init: fold_absent_slot(), step: fold_declared_slot(name: ^algebra, position: 1) - }) - } else { - optional_absent() + match collection_roster_row_optional(head: head) { + Present { value: r } => + optional_present(value: FoldRealizesRosterOperation { + template: r.template, + row: r.row, + slots: collection_fold_slots(t: r.template) + }) + Absent => + if head == ^fold_list { + fold_realizes_declaration(module_path: "v2.std.algebra", decl_name: "fold_list", slots: FoldSlots { + collection: fold_declared_slot(name: ^xs, position: 0), init: fold_declared_slot(name: ^empty, position: 1), step: fold_declared_slot(name: ^cons, position: 2) + }) + } else if head == ^fold_list_right { + fold_realizes_declaration(module_path: "v2.std.algebra", decl_name: "fold_list_right", slots: FoldSlots { + collection: fold_declared_slot(name: ^xs, position: 0), init: fold_declared_slot(name: ^empty, position: 1), step: fold_declared_slot(name: ^snoc, position: 2) + }) + } else if head == ^fold_node { + fold_realizes_declaration(module_path: "v2.std.node", decl_name: "fold_node", slots: FoldSlots { + collection: fold_declared_slot(name: ^n, position: 0), init: fold_absent_slot(), step: fold_declared_slot(name: ^algebra, position: 1) + }) + } else { + optional_absent() + } + } +} + +// THE FAMILY WHOSE STEP IS AUTHORED: fold_family_realization minus the callback operations. The +// accumulator-copy lens asks whether an AUTHORED step copies its accumulator; a synthesized callback step +// conses one cell onto a carrier no author wrote, so it is not that lens's subject and is not offered to it. +fn fold_family_authored_step_realization(head: Symbol) -> Optional { + match fold_family_realization(head: head) { + Absent => optional_absent() + Present { value: r } => + match fold_realization_form(r: r) { + RosterAuthoredStep => optional_present(value: r) + RosterCallbackStep { realization: _ } => optional_absent() + } + } +} + +fn fold_realization_form(r: FoldRealization) -> FoldRosterForm { + match r { + FoldRealizesRosterOperation { template: _, row: row, slots: _ } => fold_roster_form(row: row) + FoldRealizesDeclaration { declaration: _, slots: _ } => RosterAuthoredStep } } @@ -758,6 +851,16 @@ fn fold_lowering_step_form_unresolved_diagnostic(n: Node) -> Diagnostic { } } +// A callback operation's step exists only LOWERED (v2.compiler.body_lowering_fold builds it around the +// callback's lowered body), so the raw-call encoding has no step to carry for it and says so. +fn fold_lowering_callback_step_unbuilt_diagnostic(n: Node) -> Diagnostic { + Diagnostic { + reason: ^fold_lowering_callback_step_unbuilt, + at: node_locus(node: n), + correction: Unavailable { reason: ExternalContractUnknown } + } +} + fn fold_carrier_binder_atom(binder: Symbol) -> Node { node_synthetic( kind: TypeNode { connective: Atom { identity: binder } }, @@ -834,13 +937,76 @@ fn fold_realization_is_at(head: Symbol, path: String) -> Bool { } } +// Every collection roster operation -- the fold and each callback operation -- hands its step one member. +fn fold_roster_row_is_at(path: String) -> Bool { + any(xs: collection_roster_rows, predicate: fn(r) { r.row.module_path + "." + r.row.decl_name == path }) +} + +// THE ORDER A FOLD-REALIZED LOOP VISITS ITS DOMAIN IS A FACT OF ITS REALIZATION, and the realization says +// it. The Loop's encoding (fold_recurrence_encoding) is one shape for every realized operation, so the +// order cannot be read off the encoding's structure; it is read off the realized declaration the Loop +// already carries (^loop_realized_declaration_edge), through this one derivation, and an executor of a +// fold-realized Loop must ask it. An authored fold step is a left fold, and fold_list is too; +// fold_list_right is the right fold. A synthesized step's order is owned by the synthesis that built it: +// map conses each result onto the carrier, which is order-preserving ONLY when the members are visited +// right to left, so its row says so; all joins with &&, whose result is the same in either order, and is +// declared left to right with the fold it is written beside. +type FoldIterationDirection + = IteratesLeftToRight + | IteratesRightToLeft + +fn callback_realization_iteration_direction(r: CallbackRealization) -> FoldIterationDirection { + match r { + CallbackMapsToCons => IteratesRightToLeft + CallbackAllByAnd => IteratesLeftToRight + } +} + +fn fold_roster_form_iteration_direction(f: FoldRosterForm) -> FoldIterationDirection { + match f { + RosterAuthoredStep => IteratesLeftToRight + RosterCallbackStep { realization: r } => callback_realization_iteration_direction(r: r) + } +} + +fn collection_roster_row_at(path: String) -> Optional { + fold_list(xs: collection_roster_rows, empty: optional_absent(), cons: fn(acc, r) { + match acc { + Present { value: _ } => acc + Absent => if r.row.module_path + "." + r.row.decl_name == path { optional_present(value: r) } else { acc } + } + }) +} + +// Absent: the path names no collection fold realization (fold_node walks a tree, not a sequence). +fn fold_realized_path_iteration_direction(path: String) -> Optional { + match collection_roster_row_at(path: path) { + Present { value: r } => optional_present(value: fold_roster_form_iteration_direction(f: r.form)) + Absent => + if fold_realization_is_at(head: ^fold_list, path: path) { + optional_present(value: IteratesLeftToRight) + } else if fold_realization_is_at(head: ^fold_list_right, path: path) { + optional_present(value: IteratesRightToLeft) + } else { + optional_absent() + } + } +} + +fn fold_realized_iteration_direction(realized: Node) -> Optional { + match declaration_reference_path_optional(node: realized) { + Absent => optional_absent() + Present { value: path } => fold_realized_path_iteration_direction(path: qualified_name_to_dotted_string(qn: path)) + } +} + // Absent: the realized edge names no fold family declaration, so this Loop is not a fold realization. fn fold_realized_member_role(realized: Node) -> Optional { match declaration_reference_path_optional(node: realized) { Absent => optional_absent() Present { value: path } => let dotted = qualified_name_to_dotted_string(qn: path) - if fold_realization_is_at(head: collection_fold_head, path: dotted) + if fold_roster_row_is_at(path: dotted) || fold_realization_is_at(head: ^fold_list, path: dotted) || fold_realization_is_at(head: ^fold_list_right, path: dotted) { optional_present(value: FoldStepTakesCollectionMember) @@ -1101,8 +1267,20 @@ fn fold_call_operands_from_step( } } +// A CALLBACK OPERATION'S OPERANDS, AT THEIR AUTHORED NODES: the head, the collection, and the +// callback function value whose one binder becomes the synthesized step's member formal. The step and +// init are not here because the call does not carry them; v2.compiler.body_lowering_fold builds both +// from `realization`, around the callback's lowered body. +type CallbackCallOperands { + head: Node + collection: Node + callback: Node + realization: CallbackRealization +} + type FoldCallLowering = FoldCallLowered { operands: FoldCallOperands } + | FoldCallCallbackLowered { operands: CallbackCallOperands } | FoldCallNotAFold { diagnostic: Diagnostic } | FoldCallStepFormUnresolved { diagnostic: Diagnostic } | FoldCallStepShapeInvalid { diagnostic: Diagnostic } @@ -1143,11 +1321,12 @@ fn fold_call_lowering_from(fold_call: Node, source: FoldCollectionSource) -> Fol Absent => FoldCallNotAFold { diagnostic: fold_lowering_not_a_fold_call_diagnostic(n: fold_call) } Present { value: realization } => - fold_call_lowering_of( - fold_call: fold_call, - slots: fold_slots_for_source(slots: fold_realization_slots(r: realization), source: source), - source: source - ) + let slots = fold_slots_for_source(slots: fold_realization_slots(r: realization), source: source) + match fold_realization_form(r: realization) { + RosterAuthoredStep => fold_call_lowering_of(fold_call: fold_call, slots: slots, source: source) + RosterCallbackStep { realization: callback } => + fold_call_callback_lowering_of(fold_call: fold_call, slots: slots, source: source, realization: callback) + } } } } @@ -1177,6 +1356,51 @@ fn fold_call_lowering_of(fold_call: Node, slots: FoldSlots, source: FoldCollecti } } +// A CALLBACK IS A ONE-BINDER FUNCTION VALUE. Its binder is the member and its body is what each member +// contributes; a callback with any other binder count is malformed here, where it is in hand, and refuses +// as shape_invalid. A callback passed by name is the same counted frontier a step passed by name is. +fn fold_callback_has_one_binder(parts: Optional) -> Bool { + match parts { + Present { value: p } => + match p.binders { + Cons { head: _, tail: Empty } => true + _ => false + } + Absent => false + } +} + +fn fold_call_callback_lowering_of(fold_call: Node, slots: FoldSlots, source: FoldCollectionSource, realization: CallbackRealization) -> FoldCallLowering { + let one_binder = match fold_call_step_form(fold_call: fold_call, slots: slots) { + StepByName => optional_absent() + StepFnLiteral { literal: fn_literal } => optional_present(value: fold_callback_has_one_binder(parts: fn_literal_parts_optional(fn_literal: fn_literal))) + StepArrowLambda { lambda: lambda } => optional_present(value: fold_callback_has_one_binder(parts: arrow_lambda_parts_optional(lambda: lambda))) + } + match one_binder { + Absent => FoldCallStepFormUnresolved { diagnostic: fold_lowering_step_form_unresolved_diagnostic(n: fold_call) } + Present { value: has_one } => + if !has_one { + FoldCallStepShapeInvalid { diagnostic: fold_lowering_shape_invalid_diagnostic(n: fold_call) } + } else { + match fold_call_head_node(fold_call: fold_call) { + Absent => FoldCallNotAFold { diagnostic: fold_lowering_not_a_fold_call_diagnostic(n: fold_call) } + Present { value: head_node } => + match fold_call_slot_value(fold_call: fold_call, slot: slots.step) { + Absent => FoldCallStepShapeInvalid { diagnostic: fold_lowering_shape_invalid_diagnostic(n: fold_call) } + Present { value: callback } => + match fold_call_collection(fold_call: fold_call, slots: slots, source: source) { + Absent => FoldCallOperandAbsent { diagnostic: fold_lowering_operand_absent_diagnostic(n: fold_call) } + Present { value: collection } => + FoldCallCallbackLowered { + operands: CallbackCallOperands { head: head_node, collection: collection, callback: callback, realization: realization } + } + } + } + } + } + } +} + // THE ACCUMULATOR BINDER IS A FACT OF THE STEP ALONE: the step's first binder, wherever the // collection comes from. The accumulator-copy lens reads it on the raw call, where a piped fold's // collection is not in the call, so it asks the step alone. It is Absent exactly where fold_call_lowering yields no @@ -1185,7 +1409,7 @@ fn fold_call_step_carrier(fold_call: Node) -> Optional { match fold_call_head_symbol(fold_call: fold_call) { Absent => optional_absent() Present { value: head } => - match fold_family_realization(head: head) { + match fold_family_authored_step_realization(head: head) { Absent => optional_absent() Present { value: realization } => match fold_call_step_form(fold_call: fold_call, slots: fold_realization_slots(r: realization)) { @@ -1211,6 +1435,7 @@ fn fold_call_step_carrier(fold_call: Node) -> Optional { fn fold_call_to_encoding(fold_call: Node) -> Outcome { match fold_call_lowering(fold_call: fold_call) { FoldCallLowered { operands: operands } => outcome_accepted(value: fold_recurrence_encoding(operands: operands)) + FoldCallCallbackLowered { operands: operands } => outcome_rejected(d: fold_lowering_callback_step_unbuilt_diagnostic(n: operands.head)) FoldCallNotAFold { diagnostic: d } => outcome_rejected(d: d) FoldCallStepFormUnresolved { diagnostic: d } => outcome_rejected(d: d) FoldCallStepShapeInvalid { diagnostic: d } => outcome_rejected(d: d) diff --git a/src/v2/lens/complexity_accumulator_copy/analyze.dag b/src/v2/lens/complexity_accumulator_copy/analyze.dag index 02438b5e818..a8098d7865e 100644 --- a/src/v2/lens/complexity_accumulator_copy/analyze.dag +++ b/src/v2/lens/complexity_accumulator_copy/analyze.dag @@ -754,7 +754,7 @@ fn analyze(node: Node, carriers: List, bindings: List match sequence_left_symbol(cap: cap) { Present { value: head } => - match v2.compiler.fold_lowering.fold_family_realization(head: head) { + match v2.compiler.fold_lowering.fold_family_authored_step_realization(head: head) { Present { value: _ } => match fold_family_carrier(call_capture: cap) { Present { value: carrier } => @@ -853,7 +853,7 @@ fn count_fold_sites(n: Node, bound_only: Bool) -> Int { match sequence_left_symbol(cap: cap) { Absent => 0 Present { value: head } => - match v2.compiler.fold_lowering.fold_family_realization(head: head) { + match v2.compiler.fold_lowering.fold_family_authored_step_realization(head: head) { Present { value: _ } => if bound_only { match fold_family_carrier(call_capture: cap) { @@ -906,7 +906,7 @@ fn primary_expr_fold_head_optional(node: Node) -> Optional { Present { value: cap } => match sequence_left_symbol(cap: cap) { Present { value: head } => - match v2.compiler.fold_lowering.fold_family_realization(head: head) { + match v2.compiler.fold_lowering.fold_family_authored_step_realization(head: head) { Present { value: _ } => optional_present(value: head) Absent => diff --git a/src/v2/std/anonymous_binder.dag b/src/v2/std/anonymous_binder.dag index f7775d06911..5e09b52e0ae 100644 --- a/src/v2/std/anonymous_binder.dag +++ b/src/v2/std/anonymous_binder.dag @@ -98,6 +98,19 @@ fn fresh_fold_carrier(step_digest: String) -> Symbol { symbol_intern_lexeme(lexeme: fold_carrier_prefix() + step_digest + ">") } +// A SYNTHESIZED STEP'S ACCUMULATOR FORMAL. A callback operation (map, filter, any, all) has no authored +// step, so v2.compiler.body_lowering_fold builds one, and its first formal needs a name no author can +// write. It is keyed by the CALLBACK's content hash for the same reason the slot is keyed by the step's: +// a callback nested inside another's body has different content, so the inner formal never hides the +// outer one. It is distinct from the slot's prefix, so the formal and the slot are never one spelling. +fn fold_accumulator_prefix() -> String { + " Symbol { + symbol_intern_lexeme(lexeme: fold_accumulator_prefix() + callback_digest + ">") +} + fn is_fold_carrier_binder(sym: Symbol) -> Bool { starts_with(s: symbol_lexeme(sym: sym), prefix: fold_carrier_prefix()) } diff --git a/src/v2/test/claim/compiler/collection_callback_realization_test.dag b/src/v2/test/claim/compiler/collection_callback_realization_test.dag new file mode 100644 index 00000000000..00803e857f8 --- /dev/null +++ b/src/v2/test/claim/compiler/collection_callback_realization_test.dag @@ -0,0 +1,261 @@ +module v2.test.claim.compiler.collection_callback_realization + +import v2.compiler.resolve { ResolvedTree } +import v2.compiler.infer { infer } +import v2.compiler.fold_lowering { FoldIterationDirection, IteratesLeftToRight, IteratesRightToLeft, fold_family_realization, fold_loop_step, fold_realization_declaration_path, fold_realized_iteration_direction, fold_realized_path_iteration_direction, fold_step_body_node } +import v2.test.claim.compiler.infer_fold_member_instance { fmi_assemble, fmi_infer, fmi_infer_tree, fmi_no_projection_refusal, fmi_reason } +import v2.std.arrow_signature { ArrowParameterOrderAbsent, ArrowParameterOrderDeclared, ArrowParameterOrderMalformed, arrow_declared_parameter_order } +import v2.std.collection { List, list_at_optional } +import v2.std.diagnostic { Accepted, Outcome, Rejected } +import v2.std.node_query { find_named_child } +import v2.std.qualified_name { declaration_reference_path_optional, lexical_reference_label_optional, qualified_name_to_dotted_string } +import v2.std.optional { Absent, Optional, Present, optional_absent, optional_present } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import std.algebra { Cons, Empty } +import v2.std.node { ComputationNode, Found, Loop, Node, Symbol, loop_realized_declaration_target, node_subtree_nodes } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// SUBJECT: a std.algebra collection callback operation (map, all) lowers through the ONE +// fold encoding (v2.compiler.fold_lowering CallbackRealization, v2.compiler.body_lowering_fold +// body_lower_callback_encoding): its head binds to its own named template row, its callback's binder is +// the synthesized step's member formal and so is typed from the domain element by role, and the step puts +// one cell on the front of the carrier. BOUNDARY: source text in, through the production assembly route +// (v2.test.claim.compiler.infer_fold_member_instance fmi_assemble), then v2.compiler.infer infer. +// RED ON THE BASE for every control here: the base realized only the fold family, so `map(` reached +// resolve as a bare name and the whole module refused resolve_reason_unbound_symbol. + +data ccr_map_records: String = "module p\n\nimport v2.std.collection { List }\n\ntype Pair {\n target: Int\n}\n\nfn f(xs: List) -> List {\n map(xs, e => e.target)\n}\n" + +data ccr_map_undeclared_field: String = "module p\n\nimport v2.std.collection { List }\n\ntype Pair {\n target: Int\n}\n\nfn f(xs: List) -> List {\n map(xs, e => e.nope)\n}\n" + +data ccr_declared_any: String = "module p\n\nimport v2.std.collection { List }\n\ntype Pair {\n target: Int\n}\n\nfn any(xs: List, predicate: fn(Pair) -> Bool) -> Bool {\n false\n}\n\nfn f(xs: List) -> Bool {\n any(xs, e => e.target == 0)\n}\n" + +data ccr_all_records: String = "module p\n\nimport v2.std.collection { List }\n\ntype Pair {\n target: Int\n}\n\nfn f(xs: List) -> Bool {\n all(xs, e => e.target == 0)\n}\n" + +data ccr_map_piped: String = "module p\n\nimport v2.std.collection { List }\n\ntype Pair {\n target: Int\n}\n\nfn f(xs: List) -> List {\n xs |> map(e => e.target)\n}\n" + +fn ccr_loops(tree: ResolvedTree) -> List { + fold(node_subtree_nodes(root: tree.root), init: [], f: fn(acc, n) { + match n.kind { + ComputationNode { behavior: Loop } => Cons { head: n, tail: acc } + _ => acc + } + }) +} + +fn ccr_loop_realized_path(l: Node) -> Optional { + match loop_realized_declaration_target(children: l.children) { + Found { target: realized } => + match declaration_reference_path_optional(node: realized) { + Present { value: path } => optional_present(value: qualified_name_to_dotted_string(qn: path)) + Absent => optional_absent() + } + _ => optional_absent() + } +} + +fn ccr_tree_realizes(tree: ResolvedTree, row: String) -> Bool { + match ccr_loops(tree: tree) { + Cons { head: l, tail: Empty } => + match ccr_loop_realized_path(l: l) { + Present { value: path } => path == row + Absent => false + } + _ => false + } +} + +// The one Loop's direction, derived from its own realized edge through the production reader. +fn ccr_tree_iterates_right_to_left(tree: ResolvedTree) -> Bool { + match ccr_loops(tree: tree) { + Cons { head: l, tail: Empty } => ccr_loop_iterates_right_to_left(l: l) + _ => false + } +} + +fn ccr_loop_iterates_right_to_left(l: Node) -> Bool { + match loop_realized_declaration_target(children: l.children) { + Found { target: realized } => ccr_direction_is(d: fold_realized_iteration_direction(realized: realized), want_right: true) + _ => false + } +} + +fn ccr_direction_is(d: Optional, want_right: Bool) -> Bool { + match d { + Present { value: IteratesRightToLeft } => want_right + Present { value: IteratesLeftToRight } => !want_right + Absent => false + } +} + +fn ccr_head_path(head: Symbol) -> String { + match fold_family_realization(head: head) { + Present { value: r } => qualified_name_to_dotted_string(qn: fold_realization_declaration_path(r: r)) + Absent => "" + } +} + +fn ccr_assembled_realizes(src: String, row: String) -> Bool { + match fmi_assemble(src: src) { + Rejected { diagnostics: _ } => false + Accepted { value: tree, diagnostics: _ } => ccr_tree_realizes(tree: tree, row: row) + } +} + +fn ccr_step_accumulator_label(step: Node) -> Optional { + match arrow_declared_parameter_order(arrow: step) { + ArrowParameterOrderDeclared { labels: labels } => list_at_optional(xs: labels, index: 0) + ArrowParameterOrderAbsent => optional_absent() + ArrowParameterOrderMalformed => optional_absent() + } +} + +fn ccr_tree_map_step_conses(tree: ResolvedTree) -> Bool { + match ccr_loops(tree: tree) { + Cons { head: l, tail: Empty } => + match fold_loop_step(loop_node: l) { + Absent => false + Present { value: step } => + match ccr_step_accumulator_label(step: step) { + Absent => false + Present { value: accumulator } => + match fold_step_body_node(step: step) { + Absent => false + Present { value: body } => + match find_named_child(root: body, name: ^tail) { + Rejected { diagnostics: _ } => false + Accepted { value: tail, diagnostics: _ } => + match lexical_reference_label_optional(node: tail) { + Present { value: label } => label == accumulator + Absent => false + } + } + } + } + } + _ => false + } +} + +// EACH FIXTURE PROGRAM IS ASSEMBLED ONCE, by a NULLARY producer enrolled in +// v2.workflow.floor_pure_producer_share floor_cross_claim_pure_producers_warm: source assembly is the real +// route these claims must exercise (the producer IS that route), and it dominates every claim, so it is paid +// once in floor preparation and each claim reads the DECIDED, portable value. Claims over one program share +// its producer. +type CcrMapRecordsReading { + reads_field: Bool + binds_row: Bool + step_conses: Bool + iterates_right_to_left: Bool +} + +fn ccr_map_records_reading() -> CcrMapRecordsReading { + match fmi_assemble(src: ccr_map_records) { + Rejected { diagnostics: _ } => CcrMapRecordsReading { reads_field: false, binds_row: false, step_conses: false, iterates_right_to_left: false } + Accepted { value: tree, diagnostics: _ } => + CcrMapRecordsReading { + reads_field: fmi_no_projection_refusal(o: fmi_infer_tree(tree: tree)), + binds_row: ccr_tree_realizes(tree: tree, row: "std.algebra.collection_map_shape"), + step_conses: ccr_tree_map_step_conses(tree: tree), + iterates_right_to_left: ccr_tree_iterates_right_to_left(tree: tree) + } + } +} + +fn ccr_map_undeclared_field_reason() -> Symbol { + fmi_reason(o: fmi_infer(src: ccr_map_undeclared_field)) +} + +fn ccr_piped_map_binds_its_row() -> Bool { + ccr_assembled_realizes(src: ccr_map_piped, row: "std.algebra.collection_map_shape") +} + +fn ccr_all_binds_its_row() -> Bool { + ccr_assembled_realizes(src: ccr_all_records, row: "std.algebra.collection_all_shape") +} + +fn ccr_declared_any_lowers_no_loop() -> Bool { + match fmi_assemble(src: ccr_declared_any) { + Rejected { diagnostics: _ } => false + Accepted { value: tree, diagnostics: _ } => + match ccr_loops(tree: tree) { + Empty => true + Cons { head: _, tail: _ } => false + } + } +} + +// (1) MAP OVER RECORDS READS A FIELD: the callback's binder `e` is the step's member formal, typed by the +// domain element Pair, so `e.target` projects a declared field and infer raises no projection refusal. +test fn ccr_map_over_records_reads_a_field() -> Bool { + ccr_map_records_reading().reads_field +} + +// (2) A BAD FIELD REFUSES AT THE FIELD: the same map projecting a field Pair does not declare. +test fn ccr_map_undeclared_field_refuses_at_the_field() -> Bool { + ccr_map_undeclared_field_reason() == ^infer_reason_field_not_declared_on_receiver +} + +// (3) THE HEAD BINDS TO ITS OWN ROW, for every realized callback operation and for the piped spelling. +test fn ccr_map_head_binds_its_row() -> Bool { + ccr_map_records_reading().binds_row +} + +test fn ccr_piped_map_head_binds_its_row() -> Bool { + ccr_piped_map_binds_its_row() +} + +// all's body joins through &&, which infer does not yet derive (a separate frontier), so this control +// asserts the binding and lowering, not infer acceptance. +test fn ccr_all_head_binds_its_row() -> Bool { + ccr_all_binds_its_row() +} + +// (3b) A HEAD A CORPUS DECLARATION BINDS STAYS AN ORDINARY CALL. filter and any are NOT realized, +// because v2.std.algebra declares ordinary fns of those names and lowering classifies a head by spelling; +// a realized row would lower such a call to a Loop that resolve refuses as shadowed +// (gunbc.recurring_failure_mode collection_callback_operation_unrealized_in_v2_lowering). The guard: a +// module declaring its own `any` and calling it positionally assembles, and lowers to no Loop. Adding +// any's row to v2.compiler.fold_lowering collection_roster_rows makes this control red. +test fn ccr_declared_any_stays_an_ordinary_call() -> Bool { + ccr_declared_any_lowers_no_loop() +} + +// (4) THE STRUCTURAL COST CONTROL: map's synthesized step body is a construct whose `tail` is the step's +// OWN accumulator formal (declared position 0) -- one cell consed onto the carrier, O(1) per member, never +// a snoc or append over it. This is the control that measures THIS lowering: no executor reads a +// fold-realized Loop's encoding today (gunbc.recurring_failure_mode +// fold_realized_loop_carries_no_iteration_direction), so an eval_steps figure measures the seed path. +test fn ccr_map_step_conses_onto_its_accumulator() -> Bool { + ccr_map_records_reading().step_conses +} + +// (5) THE LOOP SAYS WHICH WAY IT RUNS. map's synthesized step conses onto the carrier, which preserves +// order only right to left, and the order is a fact of the realization rather than of the encoding +// (v2.compiler.fold_lowering FoldIterationDirection). On the real route the one map Loop's realized edge +// derives IteratesRightToLeft. Red if the reader is deleted or map's synthesis is declared left to right. +test fn ccr_map_loop_iterates_right_to_left() -> Bool { + ccr_map_records_reading().iterates_right_to_left +} + +// The same reader at supplied realized paths: the authored fold and all run left to right; fold_list and +// fold_list_right, which share one encoding, are told apart by their realized declarations. +test fn ccr_fold_and_all_iterate_left_to_right() -> Bool { + ccr_direction_is(d: fold_realized_path_iteration_direction(path: "std.algebra.collection_fold_shape"), want_right: false) + && ccr_direction_is(d: fold_realized_path_iteration_direction(path: "std.algebra.collection_all_shape"), want_right: false) +} + +test fn ccr_fold_list_and_fold_list_right_are_told_apart() -> Bool { + ccr_direction_is(d: fold_realized_path_iteration_direction(path: ccr_head_path(head: ^fold_list)), want_right: false) + && ccr_direction_is(d: fold_realized_path_iteration_direction(path: ccr_head_path(head: ^fold_list_right)), want_right: true) +} + +// A path that names no collection fold derives no direction, rather than a default. +test fn ccr_a_non_fold_path_has_no_direction() -> Bool { + match fold_realized_path_iteration_direction(path: "std.algebra.collection_map_shape_not") { + Absent => true + Present { value: _ } => false + } +} diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index 78c2d20bc7e..dd6c84d5f29 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -833,6 +833,11 @@ import v2.std.algebra { any } // tpb_assemble and inspected one Bool. The rows now read three nullary producers, one per program -- the // call-projection program's producer serving two rows -- whose values are a record of two Bools and two // Bools. WARM: one assembly is more than one claim's budget. +// THE collection_callback_realization PROGRAMS ARE ASSEMBLED ONCE EACH. Each claim assembled a one-module +// source through fmi_assemble (and, for two, infer) and inspected one Bool or Symbol, well over the new-witness +// eval-step cap. The rows now read five nullary producers, one per program -- the map-over-records program's +// producer, a record of three Bools, serving three rows -- and the others a Symbol and three Bools. WARM: one +// assembly is more than one claim's budget. data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.parameter_reference.pr_program_parameter_paths", "v2.test.claim.parameter_reference.pr_program_grounding", @@ -1477,7 +1482,12 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.claim.compiler.infer_fold_member_instance.fmi_body_not_carrier_reason", "v2.test.claim.value_base_projection.vbp_call_projection_reading", "v2.test.claim.value_base_projection.vbp_field_spelled_like_a_param_keeps_field", - "v2.test.claim.value_base_projection.vbp_undeclared_field_is_carried" + "v2.test.claim.value_base_projection.vbp_undeclared_field_is_carried", + "v2.test.claim.compiler.collection_callback_realization.ccr_map_records_reading", + "v2.test.claim.compiler.collection_callback_realization.ccr_map_undeclared_field_reason", + "v2.test.claim.compiler.collection_callback_realization.ccr_piped_map_binds_its_row", + "v2.test.claim.compiler.collection_callback_realization.ccr_all_binds_its_row", + "v2.test.claim.compiler.collection_callback_realization.ccr_declared_any_lowers_no_loop" ] // grammar_relation_row_for_emitted HAS NOW BEEN MEASURED ON THE THIRD CONJUNCT, AND IT PASSES.