Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module gunbc.recurring_failure_mode.unresolved_variant_pattern_renders_as_a_catch_all_binding

import std.types { NonEmptyStr }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data unresolved_variant_pattern_renders_as_a_catch_all_binding: RecurringFailureMode = RecurringFailureMode {
identity: "unresolved_variant_pattern_renders_as_a_catch_all_binding" as NonEmptyStr,

receipts: [
"an emitted Rust match arm whose variant pattern is rendered as a BARE identifier because its parent enum did not resolve (INVALID STATE: in Rust a bare identifier in pattern position that names no unit struct or in-scope variant is a fresh binding, so `Add => ...` with no `BinOp::*` in scope matches every scrutinee. HARM: the first such arm silently takes every value -- wrong-arm execution in the emitted program, with rustc reporting only unused-variable / unreachable-pattern WARNINGS, so a build without -D warnings ships it).",
"DISTINGUISHING FACTS. v1.compiler.emit_rust rust_variant_path_opt answered the Absent parent by returning the bare leaf, and all three pattern producers -- emit_variant_pattern, emit_variant_pattern_rc_aware, variant_pattern_shape_for -- consumed that answer for every non-optional variant: a failure arm that WIDENED instead of refusing (DESIGN section 5). Optional variants are not members: their Absent-parent residue lowers to Some/None through the host optional's spelling authority.",
"HOW IT WAS FOUND, AND WHAT WAS NOT FOUND. Suspected when an NFR burn-down enumeration of extdeps.languages.rust.emit rust_refusing_int_operator_helper (a flat match over nullary std.syntax BinOp) was reported to raise the //gunbc/instruments:v2-native-cli emitted-crate warning_count from 0 to 52. NOT REPRODUCED on main f006fbe9501: with the site enumerated the instrument reports exit_status=0 warning_count=0 under -D warnings and every arm renders qualified (BinOp::Add ... BinOp::NullCoalesce). No live specimen is claimed. Nor is one authorable from source today: the typechecker refuses a variant name declared by two enums anywhere in a closure, so unique_variant_parent always answers for an accepted program. The class is recorded because the state was WRITABLE by the emitter, not because it was observed in an emitted crate.",
"THE MEMBER IS THE BARE-IDENTIFIER RENDER, NOT THE UNRESOLVED PARENT, and the first cut of this repair got that wrong. Refusing every unresolved parent broke the self-host and v2-native-cli emitted crates: v2.std.node EdgeLabel `Named` (a name also declared by std.algebra and extdeps.formats.spice, so unique_variant_parent answers none) is matched over unstamped scrutinees in v2.std.compilers.body_lowering and v2.compiler.emit_produced, and renders `Named { .. }` -- a struct pattern Rust resolves by path through the authored import and never binds as a variable. The refusal is therefore keyed on the rendered shape (no brace, no paren, no path separator) with the parent unresolved. That is the census: with the narrowed arm both instruments build their emitted crates with warning_count=0, so NO current emission takes the refusal.",
"RUNG FOUND AT: silent (a writable widening arm, no refusal). RUNG NOW: mechanically preventable -- an Absent-parent arm that would render bare is a compile_error! refusal naming the variant and scrutinee (v1.compiler.emit_rust unresolved_variant_pattern_refusal) at all three producers, and the discriminating red is the generated --lib unit test unresolved_variant_pattern_refuses_instead_of_binding_bare (v1.compiler.compiler_tests_rust ct_unresolved_variant_pattern_refuses_test), which calls each producer with no parent and requires the refusal, beside a stamped-parent positive control (BinOp::Add), an optional control (std::option::Option::None) and a fielded control (an unresolved `Named` known fielded still renders `Named { .. }`). That test runs in the rust_unit_tests step, which is itself off the merge path (gunbc.rung_drop rust_unit_tests_off_the_merge_path), so the rung is held by an executing but non-blocking mechanism. The census that no current emission takes the refusal is the self-host and v2-native-cli instruments building their emitted crates. ATTAINABLE CEILING: structurally impossible -- a MatchPattern VariantPattern whose parent is a resolved declaration identity rather than an optional String, so an unresolved variant pattern has no constructor at emission. NEXT-RUNG TRIGGER, stated as the capability: v1.std.core MatchPattern VariantPattern carries a required parent-enum declaration identity stamped by resolution, SUFFICIENT FOR every pattern producer in v1.compiler.emit_rust to render a qualified path with no Absent arm to consult.",
],

evidence: [
DeclarationRef { module_path: "v1.compiler.emit_rust", decl_name: "variant_pattern_parent_unresolved", field: WholeDeclaration },
DeclarationRef { module_path: "v1.compiler.emit_rust", decl_name: "unresolved_variant_pattern_refusal", field: WholeDeclaration },
DeclarationRef { module_path: "v1.compiler.emit_rust", decl_name: "refuse_unresolved_bare_variant_pattern", field: WholeDeclaration },
DeclarationRef { module_path: "v1.compiler.compiler_tests_rust", decl_name: "ct_unresolved_variant_pattern_refuses_test", field: WholeDeclaration },
],
}
57 changes: 57 additions & 0 deletions src/v1/05_emit_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -9969,7 +9969,54 @@ fn variant_pattern_shape_str(qualified: String, rust_name: String, resolved_pare
else if is_fielded { concat(qualified, " { .. }") } else { qualified }
}

// A VARIANT PATTERN WHOSE PARENT ENUM DOES NOT RESOLVE REFUSES; IT NEVER RENDERS BARE. In Rust a
// bare identifier in pattern position that names no unit struct or in-scope variant is a fresh
// BINDING, so `Add => ...` with no `BinOp::*` in scope matches every scrutinee and the first such
// arm silently takes all of them (wrong-arm execution; rustc only warns). rust_variant_path_opt's
// Absent arm was that silent widen for every non-optional variant pattern (DESIGN section 5: a
// failure arm must refuse, never widen). Optional variants are exempt: their Absent-parent residue
// lowers to Some/None by the host optional's own spelling authority. Only the BARE-IDENTIFIER
// rendering is the hazard: a fielded or positional render (`Named { .. }`, `Wrap(_)`) is a struct or
// tuple-struct pattern Rust resolves by path and never binds as a variable, and an unresolved parent
// there already fails loudly if the name is not in scope (measured: v2.std.node EdgeLabel `Named`
// patterns over unstamped scrutinees in v2.std.compilers.body_lowering and
// v2.compiler.emit_produced render `Named { .. }` and resolve through the authored import). The
// refusal is emitted as
// compile_error! at the arm, so the build stops at the emitted location with the variant and
// scrutinee named. Class: gunbc.recurring_failure_mode
// unresolved_variant_pattern_renders_as_a_catch_all_binding.
fn variant_pattern_parent_unresolved(name: String, parent_enum: String?, scrut_type: String, emit_info: EmitGraphInfo) -> Bool {
let bare_name = qualified_last_segment(name: name)
let resolved_parent = pattern_parent_enum(name: bare_name, parent_enum: parent_enum, scrut_type: scrut_type, type_summaries: emit_info.type_summaries)
resolved_parent == none && !is_optional_variant_name(name: bare_name)
}

fn rust_pattern_is_bare_identifier(rendered: String) -> Bool {
!string_contains(s: rendered, pattern: "{") && !string_contains(s: rendered, pattern: "(") && !string_contains(s: rendered, pattern: "::")
}

fn refuse_unresolved_bare_variant_pattern(name: String, parent_enum: String?, scrut_type: String, emit_info: EmitGraphInfo, rendered: String) -> String {
if variant_pattern_parent_unresolved(name: name, parent_enum: parent_enum, scrut_type: scrut_type, emit_info: emit_info) && rust_pattern_is_bare_identifier(rendered: rendered) {
unresolved_variant_pattern_refusal(name: name, scrut_type: scrut_type)
} else {
rendered
}
}

fn unresolved_variant_pattern_refusal(name: String, scrut_type: String) -> String {
let scrut = if scrut_type == "" { "an unstamped scrutinee" } else { concat("scrutinee type `", scrut_type, "`") }
emit_rust_compile_error_expr(message: concat(
"v1.compiler.emit_rust will not render the variant pattern `", qualified_last_segment(name: name),
"` against ", scrut,
": its parent enum does not resolve, and a bare identifier pattern binds as a catch-all variable in Rust"))
}

fn emit_variant_pattern(name: String, parent_enum: String?, field_bindings: List<Node>, path_prefix: List<String>, shared_types: Set<String>, scrut_type: String, source_indices: Map<String, NewlineIndex>, emit_info: EmitGraphInfo) -> String {
refuse_unresolved_bare_variant_pattern(name: name, parent_enum: parent_enum, scrut_type: scrut_type, emit_info: emit_info,
rendered: emit_resolved_variant_pattern(name: name, parent_enum: parent_enum, field_bindings: field_bindings, path_prefix: path_prefix, shared_types: shared_types, scrut_type: scrut_type, source_indices: source_indices, emit_info: emit_info))
}

fn emit_resolved_variant_pattern(name: String, parent_enum: String?, field_bindings: List<Node>, path_prefix: List<String>, shared_types: Set<String>, scrut_type: String, source_indices: Map<String, NewlineIndex>, emit_info: EmitGraphInfo) -> String {
let bare_name = qualified_last_segment(name: name)
let resolved_parent = pattern_parent_enum(name: bare_name, parent_enum: parent_enum, scrut_type: scrut_type, type_summaries: emit_info.type_summaries)
let optional_variant = is_optional_variant_name(name: bare_name)
Expand Down Expand Up @@ -10221,6 +10268,11 @@ fn emit_pattern_rc_aware(pattern: MatchPattern, path_prefix: List<String>, rc_an
}

fn emit_variant_pattern_rc_aware(name: String, parent_enum: String?, field_bindings: List<Node>, path_prefix: List<String>, rc_analysis: RcPatternAnalysis, shared_types: Set<String>, scrut_type: String, source_indices: Map<String, NewlineIndex>, emit_info: EmitGraphInfo) -> String {
refuse_unresolved_bare_variant_pattern(name: name, parent_enum: parent_enum, scrut_type: scrut_type, emit_info: emit_info,
rendered: emit_resolved_variant_pattern_rc_aware(name: name, parent_enum: parent_enum, field_bindings: field_bindings, path_prefix: path_prefix, rc_analysis: rc_analysis, shared_types: shared_types, scrut_type: scrut_type, source_indices: source_indices, emit_info: emit_info))
}

fn emit_resolved_variant_pattern_rc_aware(name: String, parent_enum: String?, field_bindings: List<Node>, path_prefix: List<String>, rc_analysis: RcPatternAnalysis, shared_types: Set<String>, scrut_type: String, source_indices: Map<String, NewlineIndex>, emit_info: EmitGraphInfo) -> String {
let bare_name = qualified_last_segment(name: name)
let resolved_parent = pattern_parent_enum(name: bare_name, parent_enum: parent_enum, scrut_type: scrut_type, type_summaries: emit_info.type_summaries)
let optional_variant = is_optional_variant_name(name: bare_name)
Expand Down Expand Up @@ -10323,6 +10375,11 @@ fn emit_variant_pattern_rc_aware(name: String, parent_enum: String?, field_bindi
}

fn variant_pattern_shape_for(name: String, parent_enum: String?, scrut_type: String, emit_info: EmitGraphInfo) -> String {
refuse_unresolved_bare_variant_pattern(name: name, parent_enum: parent_enum, scrut_type: scrut_type, emit_info: emit_info,
rendered: resolved_variant_pattern_shape_for(name: name, parent_enum: parent_enum, scrut_type: scrut_type, emit_info: emit_info))
}

fn resolved_variant_pattern_shape_for(name: String, parent_enum: String?, scrut_type: String, emit_info: EmitGraphInfo) -> String {
let bare_name = qualified_last_segment(name: name)
let resolved_parent = pattern_parent_enum(name: bare_name, parent_enum: parent_enum, scrut_type: scrut_type, type_summaries: emit_info.type_summaries)
let optional_variant = is_optional_variant_name(name: bare_name)
Expand Down
31 changes: 31 additions & 0 deletions src/v1/compiler_tests_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,7 @@ fn ct_coercion_tests() -> String {
" // =========================================================================\n\n",
test_fns |> join(separator: "\n"),
ct_rust_btree_set_ord_eligibility_test(),
ct_unresolved_variant_pattern_refuses_test(),
ct_diagnostics_carrier_grounding_test(),
ct_render_rust_applied_type_qualified_base_test(),
ct_measure_unit_variant_exact_one_test(),
Expand Down Expand Up @@ -2331,6 +2332,36 @@ fn ct_rendered_leaf_occurrence_identity_pipeline_test() -> String {
" }\n\n")
}

// Discriminating red for gunbc.recurring_failure_mode
// unresolved_variant_pattern_renders_as_a_catch_all_binding: a nullary variant pattern whose parent
// enum does not resolve must refuse at all three pattern producers, never render as a bare
// identifier (which Rust binds as a catch-all variable). The positive control is the same pattern
// with its parent stamped; the optional control is the Absent-parent residue that lowers to None;
// the fielded control is an unresolved parent whose render is braced, which is not the hazard.
fn ct_unresolved_variant_pattern_refuses_test() -> String {
concat(
" #[test]\n",
" fn unresolved_variant_pattern_refuses_instead_of_binding_bare() {\n",
" let source_indices = std::rc::Rc::new(HashMap::new());\n",
" let empty_emit = crate::v1_compiler_infer_emit_info::empty_emit_graph_info();\n",
" let no_fields = std::rc::Rc::new(im::Vector::new());\n",
" let no_path = std::rc::Rc::new(im::Vector::new());\n",
" let no_shared = std::rc::Rc::new(im::OrdSet::new());\n",
" let unresolved = crate::v1_compiler_emit_rust::emit_variant_pattern(\"Add\".to_string(), None, no_fields.clone(), no_path.clone(), no_shared.clone(), String::new(), source_indices.clone(), empty_emit.clone());\n",
" assert!(unresolved.starts_with(\"compile_error!(\") && unresolved.contains(\"`Add`\"), \"unresolved parent must refuse, got {}\", unresolved);\n",
" let unresolved_rc = crate::v1_compiler_emit_rust::emit_variant_pattern_rc_aware(\"Add\".to_string(), None, no_fields.clone(), no_path.clone(), crate::v1_compiler_emit_rust::empty_rc_pattern_analysis(), no_shared.clone(), String::new(), source_indices.clone(), empty_emit.clone());\n",
" assert!(unresolved_rc.starts_with(\"compile_error!(\"), \"rc-aware producer must refuse, got {}\", unresolved_rc);\n",
" let unresolved_shape = crate::v1_compiler_emit_rust::variant_pattern_shape_for(\"Add\".to_string(), None, String::new(), empty_emit.clone());\n",
" assert!(unresolved_shape.starts_with(\"compile_error!(\"), \"shape producer must refuse, got {}\", unresolved_shape);\n",
" let resolved = crate::v1_compiler_emit_rust::emit_variant_pattern(\"Add\".to_string(), Some(\"BinOp\".to_string()), no_fields.clone(), no_path.clone(), no_shared.clone(), String::new(), source_indices.clone(), empty_emit.clone());\n",
" assert_eq!(resolved, \"BinOp::Add\");\n",
" let optional = crate::v1_compiler_emit_rust::emit_variant_pattern(\"Absent\".to_string(), None, no_fields, no_path, no_shared, String::new(), source_indices, empty_emit);\n",
" assert_eq!(optional, \"std::option::Option::None\");\n",
" let fielded_emit = std::rc::Rc::new(crate::v1_compiler_infer_emit_info::EmitGraphInfo { fielded_variants: std::rc::Rc::new(im::OrdSet::unit(\"Named\".to_string())), ..(*crate::v1_compiler_infer_emit_info::empty_emit_graph_info()).clone() });\n",
" assert_eq!(crate::v1_compiler_emit_rust::variant_pattern_shape_for(\"Named\".to_string(), None, String::new(), fielded_emit), \"Named { .. }\", \"a braced render is a struct pattern, never a binding, and is not refused\");\n",
" }\n\n")
}

fn ct_rust_btree_set_ord_eligibility_test() -> String {
concat(
" fn shaped_type_node(name: &str, children: Vec<std::rc::Rc<crate::v1_std_core::Node>>) -> std::rc::Rc<crate::v1_std_core::Node> {\n",
Expand Down
87 changes: 87 additions & 0 deletions src/v1/stage0/src/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4195,6 +4195,93 @@ mod compiler_tests {
);
}

#[test]
fn unresolved_variant_pattern_refuses_instead_of_binding_bare() {
let source_indices = std::rc::Rc::new(HashMap::new());
let empty_emit = crate::v1_compiler_infer_emit_info::empty_emit_graph_info();
let no_fields = std::rc::Rc::new(im::Vector::new());
let no_path = std::rc::Rc::new(im::Vector::new());
let no_shared = std::rc::Rc::new(im::OrdSet::new());
let unresolved = crate::v1_compiler_emit_rust::emit_variant_pattern(
"Add".to_string(),
None,
no_fields.clone(),
no_path.clone(),
no_shared.clone(),
String::new(),
source_indices.clone(),
empty_emit.clone(),
);
assert!(
unresolved.starts_with("compile_error!(") && unresolved.contains("`Add`"),
"unresolved parent must refuse, got {}",
unresolved
);
let unresolved_rc = crate::v1_compiler_emit_rust::emit_variant_pattern_rc_aware(
"Add".to_string(),
None,
no_fields.clone(),
no_path.clone(),
crate::v1_compiler_emit_rust::empty_rc_pattern_analysis(),
no_shared.clone(),
String::new(),
source_indices.clone(),
empty_emit.clone(),
);
assert!(
unresolved_rc.starts_with("compile_error!("),
"rc-aware producer must refuse, got {}",
unresolved_rc
);
let unresolved_shape = crate::v1_compiler_emit_rust::variant_pattern_shape_for(
"Add".to_string(),
None,
String::new(),
empty_emit.clone(),
);
assert!(
unresolved_shape.starts_with("compile_error!("),
"shape producer must refuse, got {}",
unresolved_shape
);
let resolved = crate::v1_compiler_emit_rust::emit_variant_pattern(
"Add".to_string(),
Some("BinOp".to_string()),
no_fields.clone(),
no_path.clone(),
no_shared.clone(),
String::new(),
source_indices.clone(),
empty_emit.clone(),
);
assert_eq!(resolved, "BinOp::Add");
let optional = crate::v1_compiler_emit_rust::emit_variant_pattern(
"Absent".to_string(),
None,
no_fields,
no_path,
no_shared,
String::new(),
source_indices,
empty_emit,
);
assert_eq!(optional, "std::option::Option::None");
let fielded_emit = std::rc::Rc::new(crate::v1_compiler_infer_emit_info::EmitGraphInfo {
fielded_variants: std::rc::Rc::new(im::OrdSet::unit("Named".to_string())),
..(*crate::v1_compiler_infer_emit_info::empty_emit_graph_info()).clone()
});
assert_eq!(
crate::v1_compiler_emit_rust::variant_pattern_shape_for(
"Named".to_string(),
None,
String::new(),
fielded_emit
),
"Named { .. }",
"a braced render is a struct pattern, never a binding, and is not refused"
);
}

#[test]
fn diagnostics_carrier_grounds_to_native_option() {
assert!(
Expand Down
Loading
Loading