diff --git a/dag/gunbc/instruments/native_emission_controls.dag b/dag/gunbc/instruments/native_emission_controls.dag index dc95e36f008..53d78642f5c 100644 --- a/dag/gunbc/instruments/native_emission_controls.dag +++ b/dag/gunbc/instruments/native_emission_controls.dag @@ -147,6 +147,37 @@ fn claim_driver_filesystem_cases() -> List { ] } +// RULE: an effectful fold step is realized as a loop in the enclosing async frame +// (v1.compiler.emit_rust emit_rust_effectful_fold_loop). Iterator::fold takes its step as a +// synchronous closure, so a step that reaches an effectful callee put that callee's `.await?` inside +// it: E0728, the shape of gunbc.auth.approval_decision_store pending_escalations. The step here +// reads Filesystem for every element and accumulates a record, as that fold does. Every path sits +// under a root no host provisions, so each read is a typed refusal and the counts are decided +// everywhere; the red rows are the record's two fields and the empty list. +type ControlReadTally { + refused: Int + seen: Int +} + +fn control_tally_absent_reads(paths: List) -> ControlReadTally { + fold(paths, init: ControlReadTally { refused: 0, seen: 0 }, f: (acc, p) => + let read = Filesystem.Read(path: p) + ControlReadTally { refused: if read.success { acc.refused } else { acc.refused + 1 }, seen: acc.seen + 1 } + ) +} + +fn control_absent_paths() -> List { + ["/gunbc-native-emission-controls-absent/a", "/gunbc-native-emission-controls-absent/b", "/gunbc-native-emission-controls-absent/c"] +} + +fn effectful_fold_step_cases() -> List { + [ + NativeEmissionCase { identity: "effectful_fold_step_counts_every_refused_read", held: control_tally_absent_reads(paths: control_absent_paths()).refused == 3 }, + NativeEmissionCase { identity: "effectful_fold_step_red_seen_counts_elements", held: control_tally_absent_reads(paths: control_absent_paths()).seen == 3 }, + NativeEmissionCase { identity: "effectful_fold_step_red_empty_keeps_init", held: control_tally_absent_reads(paths: []).seen == 0 } + ] +} + data native_emission_expected_identities: List = [ "set_struct_eq_same_members_in_another_order", "set_struct_eq_red_bare_field_differs", @@ -163,11 +194,14 @@ data native_emission_expected_identities: List = [ "filter_guard_receipt_blank_tail_accepts", "filter_guard_receipt_red_nonblank_tail_refuses", "filter_guard_receipt_red_empty_tag_short_circuits", - "claim_driver_filesystem_absent_path_read_is_refused" + "claim_driver_filesystem_absent_path_read_is_refused", + "effectful_fold_step_counts_every_refused_read", + "effectful_fold_step_red_seen_counts_elements", + "effectful_fold_step_red_empty_keeps_init" ] fn native_emission_cases() -> List { - concat(concat(header_bound_cases(), filter_in_branch_condition_cases()), claim_driver_filesystem_cases()) + concat(concat(concat(header_bound_cases(), filter_in_branch_condition_cases()), claim_driver_filesystem_cases()), effectful_fold_step_cases()) } fn case_row(c: NativeEmissionCase) -> String { diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 627621e927e..ef7e4779353 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12564,6 +12564,12 @@ fn emit_typed_collection_lambda(lambda_expr: Node, elem_type_str: String, regist } } +// A unit-element accumulator rendering is an inference gap, not a type, so both fold realizations +// leave it to the target's inference rather than annotate it. +fn rust_fold_safe_acc_type(acc_type_str: String) -> String { + if acc_type_str == "Rc>" || acc_type_str == "Vec<()>" || acc_type_str == "Option<()>" { "_" } else { acc_type_str } +} + fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str: String, elem_borrowed: Bool, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { match lambda_expr.expr_data { ExprLambda => @@ -12571,7 +12577,7 @@ fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str let bd = lambda_body(texpr: lambda_expr) let pn = lambda_expr.children |> skip(1) - let safe_acc_type = if acc_type_str == "Rc>" || acc_type_str == "Vec<()>" || acc_type_str == "Option<()>" { "_" } else { acc_type_str } + let safe_acc_type = rust_fold_safe_acc_type(acc_type_str: acc_type_str) let fallback_types = ps |> enumerate |> map(pair => if pair.first == 0 { safe_acc_type } else { elem_type_str } ) @@ -12782,13 +12788,91 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod Present { value: a } => fold_lambda_element_unused(lambda_expr: arg_value(n: a), source_indices: scope.type_env.source_indices) Absent => false } - let fold_fn = match args |> skip(1) |> first { - Present { value: a } => - emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str, elem_borrowed: elem_unused, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) - Absent => "compile_error!(\"missing fold function argument\")" - } let iter_template = if elem_unused { replace(sharing.iter_owned, ".cloned()", "") } else { sharing.iter_owned } - concat(apply_type_template1(template: iter_template, arg0: recv_str), ".fold(", init_str, ", ", fold_fn, ")") + let iter_str = apply_type_template1(template: iter_template, arg0: recv_str) + let step_awaits = match args |> skip(1) |> first { + Present { value: a } => rust_expr_reaches_awaited_call(n: arg_value(n: a), registry: registry, source_indices: scope.type_env.source_indices) + Absent => false + } + if step_awaits { + emit_rust_effectful_fold_loop(lambda_expr: fold_lambda_node, iter_str: iter_str, init_str: init_str, acc_type_str: lambda_acc_type_str, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) + } else { + let fold_fn = match args |> skip(1) |> first { + Present { value: a } => + emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str, elem_borrowed: elem_unused, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) + Absent => "compile_error!(\"missing fold function argument\")" + } + concat(iter_str, ".fold(", init_str, ", ", fold_fn, ")") + } +} + +// A FOLD IS A BOUNDED LOOP, AND `Iterator::fold` IS ONLY ONE REALIZATION OF IT -- THE ONE THAT +// PRESUMES A PURE STEP. The step is handed to fold as a closure, and a Rust closure is a separate, +// synchronous frame: an effectful callee inside it is emitted, by the one authority every call site +// asks (item_is_effectful_callee, and the typed service-call predicate), with `.await?`, which is +// E0728 in that frame (four sites on the approval broker's native closure, among them +// gunbc.auth.approval_decision_store pending_escalations). The closure is the wrong link, not the +// await: the enclosing fn is already async BECAUSE it reaches that effect, so the step belongs in +// the enclosing frame, where both the await and the `?` mean what they mean everywhere else in the +// fn -- the effect's refusal propagates out of the fold exactly as it would out of a let. That is +// the same move the tail-call lowering makes for effectful recursion (a loop over a mutable state +// slot in the caller's frame, not a recursive async call), applied to the Loop behavior fold +// already is. The step's parameters are bound as lets per element in declaration order, so the +// body is emitted by the ordinary expression emitter against the same lambda scope the closure +// realization uses; nothing about the body is re-derived here. +fn emit_rust_effectful_fold_loop(lambda_expr: Node, iter_str: String, init_str: String, acc_type_str: String, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { + match lambda_expr.expr_data { + ExprLambda => + let si = scope.type_env.source_indices + let ps = lambda_param_names_at(texpr: lambda_expr, source_indices: si) + let pn = lambda_expr.children |> skip(1) + let acc_param_type = rust_fold_safe_acc_type(acc_type_str: acc_type_str) + let acc_name = match ps |> first { Present { value: n } => n Absent => "_" } + let elem_name = match ps |> skip(1) |> first { Present { value: n } => n Absent => "_" } + let lambda_scope = lambda_scope_from_children(scope: scope, params: ps, param_nodes: pn) + let body_str = emit_typed_expr(texpr: lambda_body(texpr: lambda_expr), registry: registry, scope: lambda_scope, depth: depth, shared_types: shared_types, emit_info: emit_info, fuel: 1024) + let acc_ident = if acc_name == "_" { "_" } else { emit_ident(name: acc_name, target: Rust) } + let elem_ident = if elem_name == "_" { "_" } else { emit_ident(name: elem_name, target: Rust) } + let acc_decl = if acc_param_type == "_" || acc_param_type == "" { "" } else { concat(": ", acc_param_type) } + let acc_unwrap = if acc_name != "_" && set_contains(emit_info.owned_bindings, acc_name) { + concat("let ", acc_ident, " = v1_rt::take_owned(", acc_ident, "); ") + } else { "" } + concat( + "{ let mut __fold_acc", acc_decl, " = ", init_str, "; ", + "for __fold_elem in ", iter_str, " { ", + "let ", acc_ident, " = __fold_acc; ", acc_unwrap, + "let ", elem_ident, " = __fold_elem; ", + "__fold_acc = { ", body_str, " }; ", + "} __fold_acc }" + ) + _ => emit_rust_compile_error_expr(message: "effectful fold step is not a lambda: an effectful function value has no synchronous closure realization and no loop realization") + } +} + +// DOES EMITTING THIS EXPRESSION PRODUCE AN `.await?` -- asked of the same two authorities that write +// one: a source call whose callee item_is_effectful_callee, and a service op through a receiver +// is_typed_service_call_receiver admits. Nested lambdas are walked too: an await anywhere inside the +// step forces the step into the async frame, and a nested fold realizes its own loop by this rule. +fn rust_expr_reaches_awaited_call(n: Node, registry: Map, source_indices: Map) -> Bool { + let here = match n.expr_data { + ExprCall { call_semantics: cs, descent_evidence: _ } => + match call_semantics_target(cs: cs) { + SourceDeclarationCall { owner_module_path: owner, decl_name: decl } => + match lookup_item_by_identity(registry: registry, id: DeclaredCallableIdentity { owner_module_path: owner, decl_name: decl }) { + Present { value: info } => item_is_effectful_callee(info: info) + Absent => false + } + _ => false + } + ExprMethodCall { method_semantics: ms } => + match ms { + Present { value: ServiceMethodSemantics { service_name: _, op_params: _ } } => + is_typed_service_call_receiver(receiver: method_receiver(texpr: n), source_indices: source_indices) + _ => false + } + _ => false + } + here || n.children |> any(c => rust_expr_reaches_awaited_call(n: c, registry: registry, source_indices: source_indices)) } fn fold_lambda_element_unused(lambda_expr: Node, source_indices: Map) -> Bool { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 0994f20b173..b60b777f449 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -27018,6 +27018,17 @@ pub fn emit_typed_collection_lambda( } } +pub fn rust_fold_safe_acc_type(acc_type_str: String) -> String { + if (((acc_type_str.clone() == "Rc>".to_string()) + || (acc_type_str.clone() == "Vec<()>".to_string())) + || (acc_type_str.clone() == "Option<()>".to_string())) + { + "_".to_string() + } else { + acc_type_str.clone() + } +} + pub fn emit_typed_fold_lambda( lambda_expr: Rc, acc_type_str: String, @@ -27045,14 +27056,7 @@ pub fn emit_typed_fold_lambda( .skip(1 as usize) .collect::>(), ); - let safe_acc_type = if (((acc_type_str.clone() == "Rc>".to_string()) - || (acc_type_str.clone() == "Vec<()>".to_string())) - || (acc_type_str.clone() == "Option<()>".to_string())) - { - "_".to_string() - } else { - acc_type_str.clone() - }; + let safe_acc_type = rust_fold_safe_acc_type(acc_type_str.clone()); let fallback_types = Rc::new({ let mut __result = Vec::new(); for pair in Rc::new( @@ -27614,22 +27618,6 @@ pub fn emit_rust_fold_method_call( ), std::option::Option::None => false, }; - let fold_fn = match args.clone().iter().cloned().skip(1 as usize).next() { - Some(a) => emit_typed_fold_lambda( - crate::v1_std_core::arg_value(a.clone()), - lambda_acc_type_str.clone(), - fold_elem_type_str.clone(), - elem_unused.clone(), - registry.clone(), - scope.clone(), - depth.clone(), - shared_types.clone(), - fold_emit_info.clone(), - ), - std::option::Option::None => { - "compile_error!(\"missing fold function argument\")".to_string() - } - }; let iter_template = if elem_unused.clone() { v1_rt::replace( sharing.iter_owned.clone(), @@ -27639,28 +27627,178 @@ pub fn emit_rust_fold_method_call( } else { sharing.iter_owned.clone() }; - v1_rt::concat( - v1_rt::concat( + let iter_str = crate::v1_compiler_emit_core_support::apply_type_template1( + iter_template.clone(), + recv_str.clone(), + ); + let step_awaits = match args.clone().iter().cloned().skip(1 as usize).next() { + Some(a) => rust_expr_reaches_awaited_call( + crate::v1_std_core::arg_value(a.clone()), + registry.clone(), + scope.type_env.clone().source_indices.clone(), + ), + std::option::Option::None => false, + }; + if step_awaits.clone() { + emit_rust_effectful_fold_loop( + fold_lambda_node.clone(), + iter_str.clone(), + init_str.clone(), + lambda_acc_type_str.clone(), + registry.clone(), + scope.clone(), + depth.clone(), + shared_types.clone(), + fold_emit_info.clone(), + ) + } else { + { + let fold_fn = match args.clone().iter().cloned().skip(1 as usize).next() { + Some(a) => emit_typed_fold_lambda( + crate::v1_std_core::arg_value(a.clone()), + lambda_acc_type_str.clone(), + fold_elem_type_str.clone(), + elem_unused.clone(), + registry.clone(), + scope.clone(), + depth.clone(), + shared_types.clone(), + fold_emit_info.clone(), + ), + std::option::Option::None => { + "compile_error!(\"missing fold function argument\")".to_string() + } + }; v1_rt::concat( v1_rt::concat( v1_rt::concat( - crate::v1_compiler_emit_core_support::apply_type_template1( - iter_template.clone(), - recv_str.clone(), + v1_rt::concat( + v1_rt::concat(iter_str.clone(), ".fold(".to_string()), + init_str.clone(), ), - ".fold(".to_string(), + ", ".to_string(), ), - init_str.clone(), + fold_fn.clone(), ), - ", ".to_string(), - ), - fold_fn.clone(), - ), - ")".to_string(), - ) + ")".to_string(), + ) + } + } } } +pub fn emit_rust_effectful_fold_loop( + lambda_expr: Rc, + iter_str: String, + init_str: String, + acc_type_str: String, + registry: Rc>>, + scope: Rc, + depth: i64, + shared_types: Rc>, + emit_info: Rc, +) -> String { + match (*lambda_expr.expr_data.clone()).clone() { + ExprData::ExprLambda => { + let si = scope.type_env.clone().source_indices.clone(); +let ps = crate::v1_std_core::lambda_param_names_at(lambda_expr.clone(), si.clone()); +let pn = Rc::new(lambda_expr.children.clone().iter().cloned().skip(1 as usize).collect::>()); +let acc_param_type = rust_fold_safe_acc_type(acc_type_str.clone()); +let acc_name = match ps.clone().first().cloned() { + Some(n) => n.clone(), + std::option::Option::None => "_".to_string(), +}; +let elem_name = match ps.clone().iter().cloned().skip(1 as usize).next() { + Some(n) => n.clone(), + std::option::Option::None => "_".to_string(), +}; +let lambda_scope = lambda_scope_from_children(scope.clone(), ps.clone(), pn.clone()); +let body_str = emit_typed_expr(crate::v1_std_core::lambda_body(lambda_expr.clone()), registry.clone(), lambda_scope.clone(), depth.clone(), shared_types.clone(), emit_info.clone(), 1024); +let acc_ident = if (acc_name.clone() == "_".to_string()) { + "_".to_string() + } else { + crate::v1_compiler_emit::emit_ident(acc_name.clone(), RenderTarget::Rust) + }; +let elem_ident = if (elem_name.clone() == "_".to_string()) { + "_".to_string() + } else { + crate::v1_compiler_emit::emit_ident(elem_name.clone(), RenderTarget::Rust) + }; +let acc_decl = if ((acc_param_type.clone() == "_".to_string()) || (acc_param_type.clone() == "".to_string())) { + "".to_string() + } else { + v1_rt::concat(": ".to_string(), acc_param_type.clone()) + }; +let acc_unwrap = if ((acc_name.clone() != "_".to_string()) && v1_rt::set_contains(&emit_info.owned_bindings.clone(), acc_name.clone())) { + v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("let ".to_string(), acc_ident.clone()), " = v1_rt::take_owned(".to_string()), acc_ident.clone()), "); ".to_string()) + } else { + "".to_string() + }; +v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("{ let mut __fold_acc".to_string(), acc_decl.clone()), " = ".to_string()), init_str.clone()), "; ".to_string()), "for __fold_elem in ".to_string()), iter_str.clone()), " { ".to_string()), "let ".to_string()), acc_ident.clone()), " = __fold_acc; ".to_string()), acc_unwrap.clone()), "let ".to_string()), elem_ident.clone()), " = __fold_elem; ".to_string()), "__fold_acc = { ".to_string()), body_str.clone()), " }; ".to_string()), "} __fold_acc }".to_string()) +}, + _ => emit_rust_compile_error_expr("effectful fold step is not a lambda: an effectful function value has no synchronous closure realization and no loop realization".to_string()), +} +} + +pub fn rust_expr_reaches_awaited_call( + n: Rc, + registry: Rc>>, + source_indices: Rc>>, +) -> bool { + stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { + let here = match (*n.expr_data.clone()).clone() { + ExprData::ExprCall { + call_semantics: cs, .. + } => match (*crate::v1_std_core::call_semantics_target(cs.clone())).clone() { + CallTargetIdentity::SourceDeclarationCall { + owner_module_path: owner, + decl_name: decl, + .. + } => match crate::v1_compiler_emit::lookup_item_by_identity( + registry.clone(), + Rc::new(DeclaredCallableIdentity { + owner_module_path: owner.clone(), + decl_name: decl.clone(), + }), + ) { + Some(info) => { + crate::v1_compiler_infer_items::item_is_effectful_callee(info.clone()) + } + std::option::Option::None => false, + }, + _ => false, + }, + ExprData::ExprMethodCall { + method_semantics: ms, + .. + } => match ms.clone().as_deref().cloned() { + Some(MethodSemantics::ServiceMethodSemantics { .. }) => { + crate::v1_compiler_infer_service::is_typed_service_call_receiver( + crate::v1_std_core::method_receiver(n.clone()), + source_indices.clone(), + ) + } + _ => false, + }, + _ => false, + }; + (here.clone() || { + let mut __found = false; + for c in n.children.clone().iter().cloned() { + if rust_expr_reaches_awaited_call( + c.clone(), + registry.clone(), + source_indices.clone(), + ) { + __found = true; + break; + } + } + __found + }) + }) +} + pub fn fold_lambda_element_unused( lambda_expr: Rc, source_indices: Rc>>,