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
41 changes: 41 additions & 0 deletions dag/test/claim/generic_item_clone_bound_witness_test.dag
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,44 @@ test fn both_triggers_render_single_bound() -> Bool {
test fn unbounded_declared_container_negative_control() -> Bool {
w_unbounded_declared_container_propagates_nothing()
}

data fn_clone_bound_wf_witness_note: String = "THIRD site, same fixpoint: naming a Clone-bounded declared type in a FN value-param or return type is exactly as ill-formed as naming it in a field (v1_fn_param_wf_needs_clone, extending the same well-formedness trigger onto v1_generic_params_needing_clone_bound rather than a second fixpoint). w_fn_wf_bound_propagates_to_bare_generic is the occurrence_binding_from_candidates<N> specimen shape (deep-heron's honest regen of dag/std/occurrence_binding.dag) reduced to a discriminating fixture: N is named only inside BindingOccurrence<N> (value-param position) and OccurrenceBindingResult<N> (return position), never as a bare N itself, so the structural trigger (v1_type_param_needs_clone_bound) alone would leave it unbounded and only the WF trigger renders `<N: Clone>` -- this is the RED-on-pre-fix / GREEN-on-fix witness. w_fn_no_wf_trigger_stays_bare is the negative control: a fn naming only unbounded declared types keeps its generic param bare, proving the WF trigger does not widen to every fn touching a declared type. w_impl_accessor_inherits_item_bound_once locks in the already-correct IMPL-side behavior (accessor impl blocks read the struct's own item-level clone_bounded_type_params via emit_item_type_params_with_clone_bounds, not a fn-grain re-derivation) by asserting the accessor impl header carries the bound exactly once, with doubled-spelling refusals mirroring both_triggers_render_single_bound above."

fn w_fn_wf_bound_propagates_to_bare_generic() -> Bool {
compile_dag_rust_emit_check(
"module fn_clone_bound_witness.wf_propagate\nimport std.algebra { FreeMonoid }\n\ntype ContainmentPath<N> {\n ancestors: FreeMonoid<N>\n terminal: N\n}\n\ntype BindingOccurrence<N> {\n containment: ContainmentPath<N>\n}\n\ntype OccurrenceBindingResult<N> {\n containment: ContainmentPath<N>\n}\n\nfn occurrence_binding_from_candidate<N>(occurrence: BindingOccurrence<N>) -> OccurrenceBindingResult<N> {\n OccurrenceBindingResult { containment: occurrence.containment }\n}\n",
"src/fn_clone_bound_witness_wf_propagate.rs",
["fn occurrence_binding_from_candidate<N: Clone>"],
["fn occurrence_binding_from_candidate<N>"]
)
}

fn w_fn_no_wf_trigger_stays_bare() -> Bool {
compile_dag_rust_emit_check(
"module fn_clone_bound_witness.wf_negative\n\ntype Holder<N> {\n tag: Int\n}\n\nfn describe_holder<N>(holder: Holder<N>) -> String {\n \"holder\"\n}\n",
"src/fn_clone_bound_witness_wf_negative.rs",
["fn describe_holder<N>"],
["fn describe_holder<N: Clone>"]
)
}

fn w_impl_accessor_inherits_item_bound_once() -> Bool {
compile_dag_rust_emit_check(
"module fn_clone_bound_witness.impl_inherit\nimport std.algebra { FreeMonoid }\n\ntype ContainmentPath<N> {\n ancestors: FreeMonoid<N>\n terminal: N\n}\n\ntype BindingOccurrence<N> {\n containment: ContainmentPath<N>\n}\n\ntype FoldState<N>\n = FoldZero\n | FoldOne { first: BindingOccurrence<N> }\n | FoldMany { first: BindingOccurrence<N>, second: BindingOccurrence<N> }\n",
"src/fn_clone_bound_witness_impl_inherit.rs",
["impl<N: Clone> FoldState<N>"],
["impl<N: Clone> FoldState<N: Clone>", "impl<N: Clone, N: Clone> FoldState<N>", "impl<N: Clone + Clone> FoldState<N>", "impl<N> FoldState<N>"]
)
}

test fn fn_wf_bound_propagation_positive() -> Bool {
w_fn_wf_bound_propagates_to_bare_generic()
}

test fn fn_wf_no_trigger_negative_control() -> Bool {
w_fn_no_wf_trigger_stays_bare()
}

test fn impl_accessor_inherits_item_bound_once() -> Bool {
w_impl_accessor_inherits_item_bound_once()
}
13 changes: 12 additions & 1 deletion src/v1/05_emit_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -4655,6 +4655,13 @@ fn emit_type_params(params: List<Node>, source_indices: Map<String, NewlineIndex
}
}

fn emit_bare_type_params(generic_param_names: List<String>) -> String {
if generic_param_names |> count == 0 { "" }
else {
concat("<", generic_param_names |> map(n => to_pascal(name: n)) |> join(separator: ", "), ">")
}
}

fn emit_type_params_with_clone_bound(params: List<Node>, clone_param: String, source_indices: Map<String, NewlineIndex>) -> String {
if params |> count == 0 { "" }
else {
Expand Down Expand Up @@ -5044,7 +5051,8 @@ fn emit_enum_shared_accessors(name: String, type_params: String, generic_param_n
concat(" pub fn ", emit_ident(name: fname, target: Rust), "(&self) -> ", ty, " {\n match self {\n", arms_str, "\n }\n }")
)
let fns_str = accessor_fns |> join(separator: "\n")
concat("impl", type_params, " ", name, type_params, " {\n", fns_str, "\n}")
let type_application_params = emit_bare_type_params(generic_param_names: generic_param_names)
concat("impl", type_params, " ", name, type_application_params, " {\n", fns_str, "\n}")
}

fn string_without_prefix(value: String, prefix: String) -> String? {
Expand Down Expand Up @@ -5318,6 +5326,9 @@ fn emit_fn_def(name: String, params: List<Node>, inferred: Node, body: Node, reg
return_is_bare_generic: return_is_bare_generic,
ret_name: ret_name,
body_is_param_ref: body_is_param_ref,
ret: inferred,
bounds: emit_info.clone_bounded_type_params,
type_decl_items: emit_info.type_decl_items,
source_indices: si
)
let needs_clone_bound = (clone_param_names |> count) > 0
Expand Down
27 changes: 26 additions & 1 deletion src/v1/stage0/src/v1_compiler_emit_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11746,6 +11746,27 @@ pub fn emit_type_params(
}
}

pub fn emit_bare_type_params(generic_param_names: Rc<Vec<String>>) -> String {
if ((generic_param_names.clone().len() as i64) == 0) {
"".to_string()
} else {
v1_rt::concat(
v1_rt::concat(
"<".to_string(),
Rc::new({
let mut __result = Vec::new();
for n in generic_param_names.clone().iter().cloned() {
__result.push(to_pascal(n.clone()));
}
__result
})
.join(&", ".to_string()),
),
">".to_string(),
)
}
}

pub fn emit_type_params_with_clone_bound(
params: Rc<Vec<Rc<Node>>>,
clone_param: String,
Expand Down Expand Up @@ -13239,6 +13260,7 @@ pub fn emit_enum_shared_accessors(
__result
});
let fns_str = accessor_fns.clone().join(&"\n".to_string());
let type_application_params = emit_bare_type_params(generic_param_names.clone());
v1_rt::concat(
v1_rt::concat(
v1_rt::concat(
Expand All @@ -13250,7 +13272,7 @@ pub fn emit_enum_shared_accessors(
),
name.clone(),
),
type_params.clone(),
type_application_params.clone(),
),
" {\n".to_string(),
),
Expand Down Expand Up @@ -13939,6 +13961,9 @@ pub fn emit_fn_def(
return_is_bare_generic.clone(),
ret_name.clone(),
body_is_param_ref.clone(),
inferred.clone(),
emit_info.clone_bounded_type_params.clone(),
emit_info.type_decl_items.clone(),
si.clone(),
);
let needs_clone_bound = ((clone_param_names.clone().len() as i64) > 0);
Expand Down
75 changes: 70 additions & 5 deletions src/v1/stage0/src/v1_compiler_trait_derive_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ pub fn trait_derive_emit_item_clone_bound_wf_propagation_note() -> String {
CACHED.with(|c: &String| c.clone())
}

pub fn trait_derive_emit_fn_clone_bound_wf_propagation_note() -> String {
thread_local! {
static CACHED: String = {
"THIRD site for the SAME well-formedness trigger (trait_derive_emit_item_clone_bound_wf_propagation_note above), applied to FN declarations rather than item (struct/enum) declarations: naming a Clone-bounded declared type G<A..> in a fn's value-param or return type is exactly as ill-formed as naming it in a field, so a fn generic parameter P earns `: Clone` when v1_fn_param_wf_needs_clone finds it occupying a Clone-bounded argument position of some declared type mentioned in a value param or the return type — reusing v1_type_expr_wf_needs_clone_param verbatim against the same v1_clone_bounded_type_params fixpoint (EmitGraphInfo.clone_bounded_type_params), not a second fixpoint or a restated predicate. This is additive to, not a replacement for, the existing structural fn trigger (v1_type_param_needs_clone_bound: bare-generic return or direct container-element usage) computed in v1_generic_params_needing_clone_bound — the two triggers answer different questions (usage-shape vs. naming-a-bounded-declared-type) and a fn param needing either earns the bound. Enum/struct IMPL surfaces (accessor impl blocks, supplemental impls) already inherit their item's bounded type_params string from emit_item_type_params_with_clone_bounds at the struct/enum decl site, so no separate IMPL-side propagation is needed there; the gap closed here is specifically free FN declarations, whose type_params were computed independently of the item-level fixpoint. Discovered live (not hypothetical) in deep-heron's honest regen of dag/std/occurrence_binding.dag: occurrence_binding_from_candidates<N> names BindingOccurrence<N> and BindingCandidate<N> (each well-formedness-bounded because their field ContainmentPath<N> is FreeMonoid<N>-derive-bounded) at value-param position without the fn declaring N: Clone, and OccurrenceBindingResult<N> at the return position — E0277 at both without this trigger.".to_string()
};
}
CACHED.with(|c: &String| c.clone())
}

pub fn trait_derive_emit_item_clone_bound_contract_fork_note() -> String {
thread_local! {
static CACHED: String = {
Expand Down Expand Up @@ -315,19 +324,29 @@ pub fn v1_generic_params_needing_clone_bound(
return_is_bare_generic: bool,
ret_name: String,
body_is_param_ref: bool,
ret: Rc<Node>,
bounds: Rc<HashMap<String, Rc<BTreeSet<String>>>>,
type_decl_items: Rc<HashMap<String, Rc<Node>>>,
source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>,
) -> Rc<Vec<String>> {
Rc::new({
let mut __result = Vec::new();
for g in generic_param_names.clone().iter().cloned() {
if v1_type_param_needs_clone_bound(
if (v1_type_param_needs_clone_bound(
g.clone(),
return_is_bare_generic.clone(),
ret_name.clone(),
body_is_param_ref.clone(),
value_params.clone(),
source_indices.clone(),
) {
) || v1_fn_param_wf_needs_clone(
g.clone(),
value_params.clone(),
ret.clone(),
bounds.clone(),
type_decl_items.clone(),
source_indices.clone(),
)) {
__result.push(g);
}
}
Expand Down Expand Up @@ -387,6 +406,20 @@ pub fn v1_item_type_param_needs_clone_bound_struct(
}
}

pub fn v1_wf_child_type_node(
ch: Rc<Node>,
source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>,
) -> Rc<Node> {
{
let resolved = child_type_node(ch.clone());
if (authored_name_at(source_indices.clone(), resolved.clone()) != "".to_string()) {
resolved.clone()
} else {
ch.clone()
}
}
}

pub fn v1_type_expr_is_bare_param(
param_name: String,
type_expr: Rc<Node>,
Expand Down Expand Up @@ -460,7 +493,7 @@ pub fn v1_type_expr_clone_impl_needs_param(
for c in type_expr.children.clone().iter().cloned() {
if v1_type_expr_clone_impl_needs_param(
param_name.clone(),
child_type_node(c.clone()),
v1_wf_child_type_node(c.clone(), source_indices.clone()),
type_decl_items.clone(),
source_indices.clone(),
) {
Expand Down Expand Up @@ -497,7 +530,7 @@ pub fn v1_declared_arg_positions_need_clone_param(
generic_param_name_at(decl_param.clone(), source_indices.clone()),
) && v1_type_expr_clone_impl_needs_param(
param_name.clone(),
child_type_node(type_arg.clone()),
v1_wf_child_type_node(type_arg.clone(), source_indices.clone()),
type_decl_items.clone(),
source_indices.clone(),
));
Expand Down Expand Up @@ -548,7 +581,7 @@ pub fn v1_type_expr_wf_needs_clone_param(
for c in type_expr.children.clone().iter().cloned() {
if v1_type_expr_wf_needs_clone_param(
param_name.clone(),
child_type_node(c.clone()),
v1_wf_child_type_node(c.clone(), source_indices.clone()),
bounds.clone(),
type_decl_items.clone(),
source_indices.clone(),
Expand Down Expand Up @@ -637,6 +670,38 @@ pub fn v1_item_param_wf_needs_clone(
}
}

pub fn v1_fn_param_wf_needs_clone(
param_name: String,
value_params: Rc<Vec<Rc<Node>>>,
ret: Rc<Node>,
bounds: Rc<HashMap<String, Rc<BTreeSet<String>>>>,
type_decl_items: Rc<HashMap<String, Rc<Node>>>,
source_indices: Rc<HashMap<String, Rc<NewlineIndex>>>,
) -> bool {
({
let mut __found = false;
for vp in value_params.clone().iter().cloned() {
if v1_type_expr_wf_needs_clone_param(
param_name.clone(),
param_node_type_expr(vp.clone()),
bounds.clone(),
type_decl_items.clone(),
source_indices.clone(),
) {
__found = true;
break;
}
}
__found
} || v1_type_expr_wf_needs_clone_param(
param_name.clone(),
ret.clone(),
bounds.clone(),
type_decl_items.clone(),
source_indices.clone(),
))
}

pub fn v1_item_clone_undecided_head(
item: Rc<Node>,
type_decl_items: Rc<HashMap<String, Rc<Node>>>,
Expand Down
Loading
Loading