diff --git a/docs/probes/underscore_named_call_order_treatment_2026-08-23.md b/docs/probes/underscore_named_call_order_treatment_2026-08-23.md new file mode 100644 index 00000000000..ebea024a5ab --- /dev/null +++ b/docs/probes/underscore_named_call_order_treatment_2026-08-23.md @@ -0,0 +1,68 @@ +# Underscore-idiom named-call order — preregistered treatment + +This is one bounded producer decision from the retained `03_ingest` emission board at +`907f19c2cc7`. It is not a partition of that board. + +## Current population + +The retained log has seven `error[E0308]: arguments to this function are incorrect` blocks in +the emitted `v2.compiler.eval` module whose callees are `eval_value_node`, +`eval_transform_node`, `eval_branch_node`, `eval_loop_node`, `eval_bind_node`, and +`eval_match_node` (two calls reach `eval_loop_node`). Each declaration uses at least one +underscore-prefixed unused parameter and each authored call supplies its caller-visible label. + +The other three diagnostics with the same rustc message are controls, not members: two are +positional calls inside fold lambdas, and one supplies `NormalizedTree` values to a function that +requires `Node`. This treatment predicts no change to those three. + +## Producer and intended fact + +`v1.compiler.emit` `order_typed_call_args` translates source named arguments into target +positional arguments. It previously compared a caller label to the exact declaration spelling. +The accepted label relation already has one authority: `v1.compiler.infer` +`call_arg_label_matches_param`, which defines declaration `_args` as caller label `args` (and also +serves inference's call-shape wall). Exact spelling therefore made emission answer the same fact +differently from inference. + +The intended fact is unique: an accepted named call is ordered by declaration parameters using +`call_arg_label_matches_param`. A bare anonymous `_` has no unique caller-visible identity and is +not reordered by name. + +## Discriminating treatment and preregistered receipt + +The witness compiles two otherwise-identical declarations and calls. The control declares +`direct_order(a, b)`; the treatment declares `underscore_order(_a, b)`. Both calls author `b` +before `a`. Their emitted Rust must contain, respectively, `direct_order(11, 23)` and +`underscore_order(11, 23)`. + +Before observing a candidate board, the registered result is: + +- all seven in-scope `v2.compiler.eval` E0308 blocks disappear rather than convert: their arity + and types already agree with the declarations, and only their positional order differs, so no + E0061 or type-mismatch successor is predicted; +- the three out-of-scope same-message blocks remain byte-identifiable by callee and enclosing + declaration; +- the control and treatment emit the same positional order; +- any successor diagnostic refutes that prediction and is reported as a conversion, not silently + counted as removal. + +## Observed paired receipt + +The prediction was tested in one remote dispatch with the annotation-only #9027 correction in +both arms. Because that correction conflicted with the older board tree, both arms took the same +resolved file from #9027; it is therefore common setup rather than a treatment difference. The +before arm was `183e5972999` plus that correction. The after arm was PR #9026 at `e2317ed0838` +(including its regenerated stage0 mirrors) plus the identical correction. + +| observation | before | after | +|---|---:|---:| +| coded rustc errors | 324 | 317 | +| E0308 | 128 | 121 | +| scoped `v2.compiler.eval` call-order identities | 7 | 0 | +| three preregistered control identities (location-reference count) | 5 | 5 | +| E0061 | 17 | 17 | + +The prediction held exactly: all seven scoped E0308 blocks vanished, none converted, and no +control moved. The paired board total is 324 rather than the authorized retained board's 316 +because the common #9027 file resolution changes that composite subject; no count is compared +across those subjects. Only the within-pair delta is claimed here. diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 94fb43ab685..38cf5048933 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -2476,12 +2476,23 @@ fn borrowed_callable_call_type(gnode: Node, typed_args: List, scope: Infer fn is_anonymous_bound_name(name: String) -> Bool { name == "_" } +fn call_param_caller_labels(param_name: String) -> List { + if param_name == "_" { + [] + } else if string_length(s: param_name) >= 1 + && substring(s: param_name, start: 0, end: 1) == "_" { + [param_name, substring(s: param_name, start: 1, end: string_length(s: param_name))] + } else { + [param_name] + } +} + fn call_arg_label_matches_param(param_name: String, arg_label: String) -> Bool { - param_name == arg_label - || param_name == "_" - || (string_length(s: param_name) >= 1 - && substring(s: param_name, start: 0, end: 1) == "_" - && substring(s: param_name, start: 1, end: string_length(s: param_name)) == arg_label) + if param_name == "_" { + true + } else { + call_param_caller_labels(param_name: param_name) |> any(caller_label => caller_label == arg_label) + } } fn param_binds_positionally(p: Node, source_indices: Map) -> Bool { diff --git a/src/v1/05_emit.dag b/src/v1/05_emit.dag index adbbca8408c..c34cdd0cd30 100644 --- a/src/v1/05_emit.dag +++ b/src/v1/05_emit.dag @@ -49,7 +49,7 @@ import v1.compiler.infer_lookup { lookup_func_sig, func_sig_if_resolved } import v1.compiler.infer { InferScope, - build_params_scope, extend_scope + build_params_scope, extend_scope, call_param_caller_labels } import v1.compiler.infer_emit_info { TypeSummary, EmitGraphInfo } @@ -303,15 +303,10 @@ fn lookup_func_sig_in_scope(scope: InferScope, name: String) -> ResolvedFuncSig? func_sig_if_resolved(lookup: lookup_func_sig(func_env: scope.func_env, type_env: scope.type_env, name: name)) } -fn typed_named_arg_matches(arg: Node, name: String, source_indices: Map) -> Bool { - let n = arg_name_at(n: arg, source_indices: source_indices) - if n == none { - false - } else { - n.value == name - } -} - +// Translate named source arguments to target positional order through infer's +// accepted-label authority. `_args` accepts exact `_args` and caller-facing +// `args`; anonymous `_` has no reorderable identity. Both indexes consume that +// bounded label set, keeping construction and lookup linear. fn order_typed_call_args(args: List, func: String, scope: InferScope) -> List { let has_unnamed = args |> any(arg => arg_name_at(n: arg, source_indices: scope.type_env.source_indices) == none) if has_unnamed { @@ -324,11 +319,18 @@ fn order_typed_call_args(args: List, func: String, scope: InferScope) -> L let n = arg_name_at(n: arg, source_indices: scope.type_env.source_indices) if n != none { map_insert(acc, n.value, arg) } else { acc } ) - let param_name_set = sig.params |> fold(init: empty_map(), f: (acc, param) => - map_insert(acc, param_node_name_at(n: param, source_indices: scope.type_env.source_indices), true) + let param_label_set = sig.params |> fold(init: empty_map(), f: (acc, param) => + call_param_caller_labels(param_name: param_node_name_at(n: param, source_indices: scope.type_env.source_indices)) + |> fold(init: acc, f: (labels, label) => map_insert(labels, label, true)) ) let ordered = sig.params |> flat_map(param => - match map_get(arg_map, param_node_name_at(n: param, source_indices: scope.type_env.source_indices)) { + let param_name = param_node_name_at(n: param, source_indices: scope.type_env.source_indices) + let matching = call_param_caller_labels(param_name: param_name) + |> flat_map(label => match map_get(arg_map, label) { + Present { value: arg } => [arg] + Absent => [] + }) + match matching |> first { Present { value: arg } => [arg] Absent => [] } @@ -338,7 +340,7 @@ fn order_typed_call_args(args: List, func: String, scope: InferScope) -> L if n == none { true } else { - emit_map_has(m: param_name_set, key: n.value) == false + emit_map_has(m: param_label_set, key: n.value) == false } ) concat(ordered, leftovers) diff --git a/src/v1/05_emit_go.dag b/src/v1/05_emit_go.dag index bb56993f3be..a6492f66237 100644 --- a/src/v1/05_emit_go.dag +++ b/src/v1/05_emit_go.dag @@ -68,7 +68,7 @@ import v1.compiler.emit { lookup_item, is_null_coalesce, emit_null_coalesce, has_nested_records_node, - typed_named_arg_matches, order_typed_call_args, + order_typed_call_args, is_tco_eligible, service_fallback_transport, effective_operation_transport, ServiceFieldSet, compute_service_fields, service_field_decls, diff --git a/src/v1/05_emit_python.dag b/src/v1/05_emit_python.dag index f5a6a8983f7..c67b2421e7a 100644 --- a/src/v1/05_emit_python.dag +++ b/src/v1/05_emit_python.dag @@ -63,7 +63,7 @@ import v1.compiler.emit { empty_emit_scope, module_emit_scope, scope_after_expr, lookup_item, - typed_named_arg_matches, order_typed_call_args, + order_typed_call_args, has_nested_records_node, is_null_coalesce, emit_null_coalesce, has_service_items, diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 638842874ba..e8214233e70 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -138,7 +138,7 @@ import v1.compiler.emit { emit_lambda_params, emit_null_coalesce, emit_list_lit_expr, emit_shared_expr, emit_typed_cast_shared, emit_string_literal, emit_simple_expr, escape_rust_interp_text, module_emit_scope, scope_after_expr, lookup_item, has_nested_records_node, - emit_data_value_json, EmitterOutcome, Emitted, Refused, is_null_coalesce, has_service_items, typed_named_arg_matches, + emit_data_value_json, EmitterOutcome, Emitted, Refused, is_null_coalesce, has_service_items, order_typed_call_args, is_tco_eligible, is_self_recursive, emit_shared_tco_expr, tco_reassign_core, is_tco_identity_passthrough, service_fallback_transport, effective_operation_transport, ServiceFieldSet, compute_service_fields, service_field_decls, diff --git a/src/v1/compiler_tests_rust.dag b/src/v1/compiler_tests_rust.dag index 46d22c2196f..c783051dc49 100644 --- a/src/v1/compiler_tests_rust.dag +++ b/src/v1/compiler_tests_rust.dag @@ -2080,13 +2080,31 @@ fn ct_call_shape_wall_witness_test() -> String { " // it, so the compile seam must too) all stay silent.\n", " let green = compile_one(\n", " \"green.dag\",\n", - " \"module green\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\\nfn named() -> Int { sub(a: 10, b: 3) }\\nfn positional() -> Int { sub(10, 3) }\\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\\n\",\n", + " \"module green\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\\nfn direct_order(a: Int, b: Int) -> Int { b }\\nfn underscore_order(_a: Int, b: Int) -> Int { b }\\nfn underscore_exact_order(_a: Int, b: Int) -> Int { b }\\nfn named() -> Int { sub(a: 10, b: 3) }\\nfn positional() -> Int { sub(10, 3) }\\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\\nfn direct_order_control() -> Int { direct_order(b: 23, a: 11) }\\nfn underscore_order_witness() -> Int { underscore_order(b: 23, a: 11) }\\nfn underscore_exact_order_control() -> Int { underscore_exact_order(b: 23, _a: 11) }\\n\",\n", " );\n", " assert!(\n", " green.diagnostics.is_empty(),\n", " \"correct call shapes must compile with NO diagnostic of any severity, got: {:?}\",\n", " green.diagnostics\n", " );\n", + " let emitted = green.files.iter()\n", + " .find(|f| f.path.ends_with(\"green.rs\"))\n", + " .expect(\"green fixture must emit its Rust module\");\n", + " assert!(\n", + " emitted.content.contains(\"direct_order(11, 23)\"),\n", + " \"control: ordinary named arguments must follow declaration order, got: {}\",\n", + " emitted.content\n", + " );\n", + " assert!(\n", + " emitted.content.contains(\"underscore_exact_order(11, 23)\"),\n", + " \"control: exact underscore-prefixed caller labels must remain accepted and follow declaration order, got: {}\",\n", + " emitted.content\n", + " );\n", + " assert!(\n", + " emitted.content.contains(\"underscore_order(11, 23)\"),\n", + " \"named arguments accepted through the declaration-side underscore idiom must follow that same declaration order, got: {}\",\n", + " emitted.content\n", + " );\n", " })\n", " .expect(\"failed to spawn thread\")\n", " .join();\n", diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index a70bfdc921b..dcb2bceb5ef 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -522,13 +522,31 @@ mod compiler_tests { // it, so the compile seam must too) all stay silent. let green = compile_one( "green.dag", - "module green\nfn sub(a: Int, b: Int) -> Int { a - b }\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\nfn named() -> Int { sub(a: 10, b: 3) }\nfn positional() -> Int { sub(10, 3) }\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\n", + "module green\nfn sub(a: Int, b: Int) -> Int { a - b }\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\nfn direct_order(a: Int, b: Int) -> Int { b }\nfn underscore_order(_a: Int, b: Int) -> Int { b }\nfn underscore_exact_order(_a: Int, b: Int) -> Int { b }\nfn named() -> Int { sub(a: 10, b: 3) }\nfn positional() -> Int { sub(10, 3) }\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\nfn direct_order_control() -> Int { direct_order(b: 23, a: 11) }\nfn underscore_order_witness() -> Int { underscore_order(b: 23, a: 11) }\nfn underscore_exact_order_control() -> Int { underscore_exact_order(b: 23, _a: 11) }\n", ); assert!( green.diagnostics.is_empty(), "correct call shapes must compile with NO diagnostic of any severity, got: {:?}", green.diagnostics ); + let emitted = green.files.iter() + .find(|f| f.path.ends_with("green.rs")) + .expect("green fixture must emit its Rust module"); + assert!( + emitted.content.contains("direct_order(11, 23)"), + "control: ordinary named arguments must follow declaration order, got: {}", + emitted.content + ); + assert!( + emitted.content.contains("underscore_exact_order(11, 23)"), + "control: exact underscore-prefixed caller labels must remain accepted and follow declaration order, got: {}", + emitted.content + ); + assert!( + emitted.content.contains("underscore_order(11, 23)"), + "named arguments accepted through the declaration-side underscore idiom must follow that same declaration order, got: {}", + emitted.content + ); }) .expect("failed to spawn thread") .join(); diff --git a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs index 28a4861a7a0..9532f0ecda6 100644 --- a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs +++ b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs @@ -337,7 +337,7 @@ pub fn ct_contracts_sidecar_witness_test() -> String { } pub fn ct_call_shape_wall_witness_test() -> 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(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(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(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(" #[test]\n".to_string(), " fn call_shape_wall_witness() {\n".to_string()), " // DISCRIMINATING RED for direct_call_shape_wall_note (04_infer). Before the\n".to_string()), " // wall, `sub(a: 10, bb: 3)` against `fn sub(a: Int, b: Int)` compiled with\n".to_string()), " // ZERO diagnostics: the per-param type walk absorbed the mislabeled arg into\n".to_string()), " // its position, the interpreter refused the same call at runtime\n".to_string()), " // (CallContractMismatch in call_function_inner), and the Rust emitter\n".to_string()), " // silently REORDERED it positionally — two realizations of one program\n".to_string()), " // disagreeing silently. The wall makes the compile seam agree with the\n".to_string()), " // runtime authority, on both of the classes the runtime refuses.\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(8 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let compile_one = |path: &str, content: &str| {\n".to_string()), " crate::v1_compiler_compile::compile_sources(\n".to_string()), " std::rc::Rc::new(im::vector![std::rc::Rc::new(\n".to_string()), " crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: path.to_string(),\n".to_string()), " content: content.to_string(),\n".to_string()), " }\n".to_string()), " )]),\n".to_string()), " crate::v1_compiler_artifact::RenderTarget::Rust,\n".to_string()), " )\n".to_string()), " };\n".to_string()), " let mislabeled = compile_one(\n".to_string()), " \"mislabel.dag\",\n".to_string()), " \"module mislabel\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn f() -> Int { sub(a: 10, bb: 3) }\\n\",\n".to_string()), " );\n".to_string()), " let unknown: Vec<_> = mislabeled.diagnostics.iter()\n".to_string()), " .filter(|d| matches!(*d.diagnostic, crate::v1_std_core::CompilerDiagnostic::CallArgumentNameUnknown { .. }))\n".to_string()), " .collect();\n".to_string()), " assert!(\n".to_string()), " !unknown.is_empty(),\n".to_string()), " \"an argument labeling a parameter that does not exist must refuse at the compile seam — the interpreter already refuses this call at runtime, and the emitter silently reorders it, got: {:?}\",\n".to_string()), " mislabeled.diagnostics\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " unknown.iter().all(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())\n".to_string()), " && crate::v1_std_core::is_interpreter_blocking_diagnostic(d.diagnostic.clone())),\n".to_string()), " \"CallArgumentNameUnknown must BLOCK — a counted advisory would still emit the silently-reordered realization\"\n".to_string()), " );\n".to_string()), " let surplus = compile_one(\n".to_string()), " \"surplus.dag\",\n".to_string()), " \"module surplus\\nfn two(a: Int, b: Int) -> Int { a + b }\\nfn f() -> Int { two(1, 2, 3) }\\n\",\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " surplus.diagnostics.iter().any(|d| matches!(*d.diagnostic, crate::v1_std_core::CompilerDiagnostic::CallPositionalSurplus { .. })),\n".to_string()), " \"a positional argument beyond the declared positional parameters must refuse — the interpreter refuses the same call (too many positional arguments), got: {:?}\",\n".to_string()), " surplus.diagnostics\n".to_string()), " );\n".to_string()), " // POSITIVE CONTROLS at ZERO diagnostics of any severity (the filtering\n".to_string()), " // lesson of codex review 45357: asserting only the absence of the\n".to_string()), " // blocking variant lets an advisory pass unnoticed). Correct labels,\n".to_string()), " // correct positional binding, and the deliberately-unused-parameter\n".to_string()), " // idiom (label `ctx` against declared `_ctx` — the interpreter accepts\n".to_string()), " // it, so the compile seam must too) all stay silent.\n".to_string()), " let green = compile_one(\n".to_string()), " \"green.dag\",\n".to_string()), " \"module green\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\\nfn named() -> Int { sub(a: 10, b: 3) }\\nfn positional() -> Int { sub(10, 3) }\\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\\n\",\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " green.diagnostics.is_empty(),\n".to_string()), " \"correct call shapes must compile with NO diagnostic of any severity, got: {:?}\",\n".to_string()), " green.diagnostics\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"call_shape_wall_witness panicked\");\n".to_string()), " }\n\n".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(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(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(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(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(" #[test]\n".to_string(), " fn call_shape_wall_witness() {\n".to_string()), " // DISCRIMINATING RED for direct_call_shape_wall_note (04_infer). Before the\n".to_string()), " // wall, `sub(a: 10, bb: 3)` against `fn sub(a: Int, b: Int)` compiled with\n".to_string()), " // ZERO diagnostics: the per-param type walk absorbed the mislabeled arg into\n".to_string()), " // its position, the interpreter refused the same call at runtime\n".to_string()), " // (CallContractMismatch in call_function_inner), and the Rust emitter\n".to_string()), " // silently REORDERED it positionally — two realizations of one program\n".to_string()), " // disagreeing silently. The wall makes the compile seam agree with the\n".to_string()), " // runtime authority, on both of the classes the runtime refuses.\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(8 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " let compile_one = |path: &str, content: &str| {\n".to_string()), " crate::v1_compiler_compile::compile_sources(\n".to_string()), " std::rc::Rc::new(im::vector![std::rc::Rc::new(\n".to_string()), " crate::v1_compiler_compile::SourceFile {\n".to_string()), " path: path.to_string(),\n".to_string()), " content: content.to_string(),\n".to_string()), " }\n".to_string()), " )]),\n".to_string()), " crate::v1_compiler_artifact::RenderTarget::Rust,\n".to_string()), " )\n".to_string()), " };\n".to_string()), " let mislabeled = compile_one(\n".to_string()), " \"mislabel.dag\",\n".to_string()), " \"module mislabel\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn f() -> Int { sub(a: 10, bb: 3) }\\n\",\n".to_string()), " );\n".to_string()), " let unknown: Vec<_> = mislabeled.diagnostics.iter()\n".to_string()), " .filter(|d| matches!(*d.diagnostic, crate::v1_std_core::CompilerDiagnostic::CallArgumentNameUnknown { .. }))\n".to_string()), " .collect();\n".to_string()), " assert!(\n".to_string()), " !unknown.is_empty(),\n".to_string()), " \"an argument labeling a parameter that does not exist must refuse at the compile seam — the interpreter already refuses this call at runtime, and the emitter silently reorders it, got: {:?}\",\n".to_string()), " mislabeled.diagnostics\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " unknown.iter().all(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())\n".to_string()), " && crate::v1_std_core::is_interpreter_blocking_diagnostic(d.diagnostic.clone())),\n".to_string()), " \"CallArgumentNameUnknown must BLOCK — a counted advisory would still emit the silently-reordered realization\"\n".to_string()), " );\n".to_string()), " let surplus = compile_one(\n".to_string()), " \"surplus.dag\",\n".to_string()), " \"module surplus\\nfn two(a: Int, b: Int) -> Int { a + b }\\nfn f() -> Int { two(1, 2, 3) }\\n\",\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " surplus.diagnostics.iter().any(|d| matches!(*d.diagnostic, crate::v1_std_core::CompilerDiagnostic::CallPositionalSurplus { .. })),\n".to_string()), " \"a positional argument beyond the declared positional parameters must refuse — the interpreter refuses the same call (too many positional arguments), got: {:?}\",\n".to_string()), " surplus.diagnostics\n".to_string()), " );\n".to_string()), " // POSITIVE CONTROLS at ZERO diagnostics of any severity (the filtering\n".to_string()), " // lesson of codex review 45357: asserting only the absence of the\n".to_string()), " // blocking variant lets an advisory pass unnoticed). Correct labels,\n".to_string()), " // correct positional binding, and the deliberately-unused-parameter\n".to_string()), " // idiom (label `ctx` against declared `_ctx` — the interpreter accepts\n".to_string()), " // it, so the compile seam must too) all stay silent.\n".to_string()), " let green = compile_one(\n".to_string()), " \"green.dag\",\n".to_string()), " \"module green\\nfn sub(a: Int, b: Int) -> Int { a - b }\\nfn ignore_ctx(_ctx: Int, b: Int) -> Int { b }\\nfn direct_order(a: Int, b: Int) -> Int { b }\\nfn underscore_order(_a: Int, b: Int) -> Int { b }\\nfn underscore_exact_order(_a: Int, b: Int) -> Int { b }\\nfn named() -> Int { sub(a: 10, b: 3) }\\nfn positional() -> Int { sub(10, 3) }\\nfn underscore_idiom() -> Int { ignore_ctx(ctx: 1, b: 2) }\\nfn direct_order_control() -> Int { direct_order(b: 23, a: 11) }\\nfn underscore_order_witness() -> Int { underscore_order(b: 23, a: 11) }\\nfn underscore_exact_order_control() -> Int { underscore_exact_order(b: 23, _a: 11) }\\n\",\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " green.diagnostics.is_empty(),\n".to_string()), " \"correct call shapes must compile with NO diagnostic of any severity, got: {:?}\",\n".to_string()), " green.diagnostics\n".to_string()), " );\n".to_string()), " let emitted = green.files.iter()\n".to_string()), " .find(|f| f.path.ends_with(\"green.rs\"))\n".to_string()), " .expect(\"green fixture must emit its Rust module\");\n".to_string()), " assert!(\n".to_string()), " emitted.content.contains(\"direct_order(11, 23)\"),\n".to_string()), " \"control: ordinary named arguments must follow declaration order, got: {}\",\n".to_string()), " emitted.content\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " emitted.content.contains(\"underscore_exact_order(11, 23)\"),\n".to_string()), " \"control: exact underscore-prefixed caller labels must remain accepted and follow declaration order, got: {}\",\n".to_string()), " emitted.content\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " emitted.content.contains(\"underscore_order(11, 23)\"),\n".to_string()), " \"named arguments accepted through the declaration-side underscore idiom must follow that same declaration order, got: {}\",\n".to_string()), " emitted.content\n".to_string()), " );\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"call_shape_wall_witness panicked\");\n".to_string()), " }\n\n".to_string()) } pub fn ct_call_shape_duplicate_wall_witness_test() -> String { diff --git a/src/v1/stage0/src/v1_compiler_emit.rs b/src/v1/stage0/src/v1_compiler_emit.rs index 1d06c415c6f..d4ab2396501 100644 --- a/src/v1/stage0/src/v1_compiler_emit.rs +++ b/src/v1/stage0/src/v1_compiler_emit.rs @@ -41,7 +41,7 @@ pub use crate::v1_compiler_emit_core_support::{ }; pub use crate::v1_compiler_emit_core_support::{EmitResult, TestProjection}; pub use crate::v1_compiler_infer::InferScope; -pub use crate::v1_compiler_infer::{build_params_scope, extend_scope}; +pub use crate::v1_compiler_infer::{build_params_scope, call_param_caller_labels, extend_scope}; pub use crate::v1_compiler_infer_emit_info::{EmitGraphInfo, TypeSummary}; use crate::v1_compiler_infer_env::GlobalBareLookupState::*; pub use crate::v1_compiler_infer_env::{authored_name, empty_symbol_index}; @@ -658,21 +658,6 @@ pub fn lookup_func_sig_in_scope( )) } -pub fn typed_named_arg_matches( - arg: Rc, - name: String, - source_indices: Rc>>, -) -> bool { - { - let n = arg_name_at(arg.clone(), source_indices.clone()); - if (n.clone() == None) { - false - } else { - (n.clone().unwrap() == name.clone()) - } - } -} - pub fn order_typed_call_args( args: Rc>>, func: String, @@ -710,16 +695,20 @@ pub fn order_typed_call_args( } }, ); - let param_name_set = sig.params.clone().iter().cloned().fold( + let param_label_set = sig.params.clone().iter().cloned().fold( v1_rt::rc_empty_map::(), |acc: Rc>, param: Rc| { - v1_rt::rc_map_insert( + call_param_caller_labels(param_node_name_at( + param.clone(), + scope.type_env.clone().source_indices.clone(), + )) + .iter() + .cloned() + .fold( acc, - param_node_name_at( - param.clone(), - scope.type_env.clone().source_indices.clone(), - ), - true, + |labels: Rc>, label: String| { + v1_rt::rc_map_insert(labels, label.clone(), true) + }, ) }, ); @@ -727,15 +716,32 @@ pub fn order_typed_call_args( let mut __result = Vec::new(); for param in sig.params.clone().iter().cloned() { __result.extend( - (*match v1_rt::map_get( - &arg_map, - param_node_name_at( + (*{ + let param_name = param_node_name_at( param.clone(), scope.type_env.clone().source_indices.clone(), - ), - ) { - Some(arg) => Rc::new(vec![arg.clone()]), - None => Rc::new(vec![]), + ); + let matching = Rc::new({ + let mut __result = Vec::new(); + for label in call_param_caller_labels(param_name.clone()) + .iter() + .cloned() + { + __result.extend( + (*match v1_rt::map_get(&arg_map, label.clone()) { + Some(arg) => Rc::new(vec![arg.clone()]), + None => Rc::new(vec![]), + }) + .iter() + .cloned(), + ); + } + __result + }); + match matching.clone().first().cloned() { + Some(arg) => Rc::new(vec![arg.clone()]), + None => Rc::new(vec![]), + } }) .iter() .cloned(), @@ -754,7 +760,7 @@ pub fn order_typed_call_args( if (n.clone() == None) { true } else { - (emit_map_has(param_name_set.clone(), n.clone().unwrap()) + (emit_map_has(param_label_set.clone(), n.clone().unwrap()) == false) } } { diff --git a/src/v1/stage0/src/v1_compiler_emit_go.rs b/src/v1/stage0/src/v1_compiler_emit_go.rs index 82888e5e842..fd073552680 100644 --- a/src/v1/stage0/src/v1_compiler_emit_go.rs +++ b/src/v1/stage0/src/v1_compiler_emit_go.rs @@ -29,7 +29,7 @@ pub use crate::v1_compiler_emit::{ empty_emit_scope, escape_go_interp_text, extract_string_interp_parts, has_nested_records_node, is_null_coalesce, is_tco_eligible, lookup_item, module_emit_scope, order_typed_call_args, scope_after_expr, seed_bindings, service_fallback_transport, service_field_decls, - test_file_path, typed_named_arg_matches, + test_file_path, }; pub use crate::v1_compiler_emit::{BlockEmitState, BoundOperation, InterpPart, ServiceFieldSet}; pub use crate::v1_compiler_emit_core_support::{ diff --git a/src/v1/stage0/src/v1_compiler_emit_python.rs b/src/v1/stage0/src/v1_compiler_emit_python.rs index f244c7fe8f5..2907754657d 100644 --- a/src/v1/stage0/src/v1_compiler_emit_python.rs +++ b/src/v1/stage0/src/v1_compiler_emit_python.rs @@ -29,7 +29,6 @@ pub use crate::v1_compiler_emit::{ has_nested_records_node, has_service_items, is_null_coalesce, is_tco_eligible, lookup_item, module_emit_scope, order_typed_call_args, scope_after_expr, seed_bindings, service_fallback_transport, service_field_ctors, service_field_decls, test_file_path, - typed_named_arg_matches, }; pub use crate::v1_compiler_emit::{BlockEmitState, BoundOperation, InterpPart, ServiceFieldSet}; pub use crate::v1_compiler_emit_core_support::{ diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 955850d42d9..7c9084f796c 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -66,8 +66,7 @@ pub use crate::v1_compiler_emit::{ is_tco_identity_passthrough, lookup_item, module_emit_scope, order_typed_call_args, render_node_type, render_tuple_parts, rust_literal_for_pattern, scope_after_expr, seed_bindings, service_fallback_transport, service_field_ctors, service_field_decls, - tco_reassign_core, transport_binding_refusal_fact, typed_named_arg_matches, - unwrap_single_field_product, + tco_reassign_core, transport_binding_refusal_fact, unwrap_single_field_product, }; pub use crate::v1_compiler_emit::{ BlockEmitState, BoundOperation, EmitterOutcome, FileResultChannel, FileResultField, FileVerb, diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index b178163f57c..2dec1153bad 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -3710,12 +3710,38 @@ pub fn is_anonymous_bound_name(name: String) -> bool { (name.clone() == "_".to_string()) } -pub fn call_arg_label_matches_param(param_name: String, arg_label: String) -> bool { - (((param_name.clone() == arg_label.clone()) || (param_name.clone() == "_".to_string())) - || (((v1_rt::string_length(¶m_name) >= 1) +pub fn call_param_caller_labels(param_name: String) -> Rc> { + if (param_name.clone() == "_".to_string()) { + Rc::new(vec![]) + } else { + if ((v1_rt::string_length(¶m_name) >= 1) && (v1_rt::substring(¶m_name, 0, 1) == "_".to_string())) - && (v1_rt::substring(¶m_name, 1, v1_rt::string_length(¶m_name)) - == arg_label.clone()))) + { + Rc::new(vec![ + param_name.clone(), + v1_rt::substring(¶m_name, 1, v1_rt::string_length(¶m_name)), + ]) + } else { + Rc::new(vec![param_name.clone()]) + } + } +} + +pub fn call_arg_label_matches_param(param_name: String, arg_label: String) -> bool { + if (param_name.clone() == "_".to_string()) { + true + } else { + { + let mut __found = false; + for caller_label in call_param_caller_labels(param_name.clone()).iter().cloned() { + if (caller_label.clone() == arg_label.clone()) { + __found = true; + break; + } + } + __found + } + } } pub fn param_binds_positionally(