diff --git a/src/v2/compiler/body_lowering_fold.dag b/src/v2/compiler/body_lowering_fold.dag index 455e6cfabb8..1100f62fd91 100644 --- a/src/v2/compiler/body_lowering_fold.dag +++ b/src/v2/compiler/body_lowering_fold.dag @@ -811,10 +811,29 @@ fn body_lower_operand_ref_optional(node: Node) -> Optional { } } +fn body_lower_is_block_delimiter_token(id: Symbol) -> Bool { + (id == ^dag_token_lbrace) || (id == ^dag_token_rbrace) +} + +fn body_lower_sequence_left_is_lbrace(captured: Node) -> Bool { + match sugar_sequence_pair_optional( + node: body_lower_unwrap_surface_shell(node: captured) + ) { + Absent => false + Present { value: pair } => + match node_atom_identity_optional( + node: body_lower_deep_unwrap_optional(node: pair.left) + ) { + Present { value: id } => id == ^dag_token_lbrace + Absent => false + } + } +} + fn body_lower_operator_atom_optional(node: Node) -> Optional { match node_atom_identity_optional(node: node) { Present { value: id } => - if dag_lex_token_symbol(sym: id) { + if dag_lex_token_symbol(sym: id) && !body_lower_is_block_delimiter_token(id: id) { optional_present( value: node_synthetic( kind: TypeNode { connective: Atom { identity: id } }, @@ -1474,9 +1493,16 @@ fn body_lower_try_fold_behavior_optional(node: Node) -> Outcome> } } +// A brace-headed sequence is a block delimiter. This matcher must not +// claim it as a call or infix: those arms would mint Transform with `{` +// as operator and drop the interior. Absent here means not this strategy; +// every call site then walks the interior. `{` is also unwritable as an +// infix operator in body_lower_operator_atom_optional. fn body_lower_try_body_lowered(node: Node) -> Outcome> { if body_lower_is_core_substrate(node: node) { outcome_accepted(value: optional_present(value: node)) + } else if body_lower_sequence_left_is_lbrace(captured: node) { + outcome_accepted(value: optional_absent()) } else { match body_lower_try_fold_behavior_optional(node: node) { Rejected { diagnostics: r } => Rejected { diagnostics: r } @@ -1832,27 +1858,21 @@ fn body_lower_body_subtree_lower(captured: Node) -> Outcome> { } } -fn body_lower_body_subtree_lower_rest(captured: Node) -> Outcome> { - match body_lower_try_body_lowered(node: captured) { - Rejected { diagnostics: r } => Rejected { diagnostics: r } - Accepted { value: found, diagnostics: _ } => - match found { - Present { value: lowered } => outcome_accepted(value: optional_present(value: lowered)) - Absent => - match body_lower_find_captured( - root: captured, - emitted: ^dag_surface_if_expr - ) { - ParseSubtreeFound { captured: if_capture } => - body_lower_try_if_from_captured(captured: if_capture) - ParseSubtreeAbsent => - match body_lower_find_captured( - root: captured, - emitted: ^dag_surface_match_expr - ) { - ParseSubtreeFound { captured: match_capture } => - body_lower_try_match_from_captured(captured: match_capture) - ParseSubtreeAbsent => +fn body_lower_body_subtree_lower_control_forms(captured: Node) -> Outcome> { + match body_lower_find_captured( + root: captured, + emitted: ^dag_surface_if_expr + ) { + ParseSubtreeFound { captured: if_capture } => + body_lower_try_if_from_captured(captured: if_capture) + ParseSubtreeAbsent => + match body_lower_find_captured( + root: captured, + emitted: ^dag_surface_match_expr + ) { + ParseSubtreeFound { captured: match_capture } => + body_lower_try_match_from_captured(captured: match_capture) + ParseSubtreeAbsent => match body_lower_find_captured( root: captured, emitted: ^dag_surface_loop_expr @@ -1860,22 +1880,32 @@ fn body_lower_body_subtree_lower_rest(captured: Node) -> Outcome> ParseSubtreeFound { captured: loop_capture } => body_lower_try_loop_expr_from_captured(captured: loop_capture) ParseSubtreeAbsent => - match body_lower_find_captured( - root: captured, - emitted: ^dag_surface_let_expr - ) { - ParseSubtreeFound { captured: let_capture } => - body_lower_try_let_in_from_captured(captured: let_capture) - ParseSubtreeAbsent => - body_lower_body_subtree_lower_from_binary(captured: captured) - } - } - } + match body_lower_find_captured( + root: captured, + emitted: ^dag_surface_let_expr + ) { + ParseSubtreeFound { captured: let_capture } => + body_lower_try_let_in_from_captured(captured: let_capture) + ParseSubtreeAbsent => + body_lower_body_subtree_lower_from_binary(captured: captured) + } } } } } +fn body_lower_body_subtree_lower_rest(captured: Node) -> Outcome> { + match body_lower_try_body_lowered(node: captured) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: found, diagnostics: _ } => + match found { + Present { value: lowered } => + outcome_accepted(value: optional_present(value: lowered)) + Absent => body_lower_body_subtree_lower_control_forms(captured: captured) + } + } +} + fn body_lower_node_is_eq_token(n: Node) -> Bool { match node_atom_identity_optional(node: body_lower_deep_unwrap_optional(node: n)) { Present { value: id } => id == ^dag_token_eq @@ -2437,12 +2467,44 @@ fn body_lower_primary_call_suffix_optional(root: Node) -> Optional { } } -fn body_lower_try_primary_ident_call(node: Node) -> Outcome> { +fn body_lower_call_sequence_node(node: Node) -> Node { let stripped = body_lower_unwrap_surface_shell(node: node) + match parse_production_emitted_identity_optional(node: stripped) { + Present { value: emitted } => + if emitted == ^dag_surface_primary_expr { + match parse_production_captured_child_optional(node: stripped) { + Present { value: captured } => captured + Absent => stripped + } + } else { + stripped + } + Absent => stripped + } +} + +fn body_lower_call_callee_optional(node: Node) -> Optional { + match body_lower_operand_ref_optional(node: node) { + Absent => optional_absent() + Present { value: callee } => + match node_atom_identity_optional(node: callee) { + Present { value: id } => + if dag_lex_token_symbol(sym: id) { + optional_absent() + } else { + optional_present(value: callee) + } + Absent => optional_present(value: callee) + } + } +} + +fn body_lower_try_primary_ident_call(node: Node) -> Outcome> { + let stripped = body_lower_call_sequence_node(node: node) match sugar_sequence_pair_optional(node: stripped) { Absent => outcome_accepted(value: optional_absent()) Present { value: pair } => - match body_lower_operand_ref_optional(node: pair.left) { + match body_lower_call_callee_optional(node: pair.left) { Absent => outcome_accepted(value: optional_absent()) Present { value: callee_atom } => match body_lower_primary_call_suffix_optional(root: pair.right) { @@ -4626,9 +4688,6 @@ fn body_lower_primary_expr(shell: Node) -> Outcome { Present { value: loop_node } => Accepted { value: loop_node, diagnostics: ld } Absent => - match body_lower_pattern_first_atom_optional(node: captured) { - Present { value: atom } => outcome_accepted(value: atom) - Absent => match body_lower_try_primary_ident_call(node: captured) { Rejected { diagnostics: r } => Rejected { diagnostics: r } Accepted { value: primary_call, diagnostics: d } => @@ -4636,6 +4695,9 @@ fn body_lower_primary_expr(shell: Node) -> Outcome { Present { value: transform } => Accepted { value: transform, diagnostics: d } Absent => + match body_lower_pattern_first_atom_optional(node: captured) { + Present { value: atom } => outcome_accepted(value: atom) + Absent => match body_lower_try_postfix_projection(node: captured) { Rejected { diagnostics: r } => match body_lower_literal_bool_atom_optional(node: captured) { diff --git a/src/v2/test/claim/body_lowering_block_body_call_test.dag b/src/v2/test/claim/body_lowering_block_body_call_test.dag new file mode 100644 index 00000000000..392ac448625 --- /dev/null +++ b/src/v2/test/claim/body_lowering_block_body_call_test.dag @@ -0,0 +1,98 @@ +module v2.test.claim.body_lowering_block_body_call + +import v2.compiler.normalize { normalize } +import v2.compiler.parse { parse_module_prepared } +import v2.compiler.program_assembly { dag_prepared_grammar } +import v2.compiler.tokenize { tokenize } +import v2.extdeps.languages.dag { dag_lex } +import v2.std.diagnostic { Accepted, Outcome, Rejected, bind_outcome } +import v2.std.grammar { node_atom_identity_optional } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import v2.std.logic { Bool } +import v2.std.node { Node } +import v2.std.node_query { + call_callee_target, + find_arrow_body_child, + find_named_child, + node_is_call +} +import v2.std.optional { Absent, Present } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data body_lowering_block_call_source: String = "module wall.same\n\nfn leaf() -> Bool = true\n\nfn rollup() -> Bool {\n leaf()\n}\n" + +fn body_lowering_block_call_parsed() -> Outcome { + match dag_prepared_grammar() { + Rejected { diagnostics: d } => Rejected { diagnostics: d } + Accepted { value: prepared, diagnostics: residue } => + bind_outcome( + o: tokenize( + text: body_lowering_block_call_source, + file: ^body_lowering_block_call_file, + rules: dag_lex() + ), + f: fn(token_stream) { + match parse_module_prepared( + tokens: token_stream, + prepared: prepared, + validation_residue: residue + ) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: artifact, diagnostics: d } => + Accepted { value: artifact.tree, diagnostics: d } + } + } + ) + } +} + +test fn body_lowering_block_call_parses_holds() -> Bool { + match body_lowering_block_call_parsed() { + Accepted { value: _, diagnostics: _ } => true + Rejected { diagnostics: _ } => false + } +} + +test fn body_lowering_block_body_call_reaches_arrow_body() -> Bool { + match body_lowering_block_call_parsed() { + Rejected { diagnostics: _ } => false + Accepted { value: parsed, diagnostics: _ } => + match normalize(parse_tree: parsed) { + Rejected { diagnostics: _ } => false + Accepted { value: tree, diagnostics: _ } => + match find_named_child(root: tree.root, name: ^grammar_production_captured_node_projection) { + Rejected { diagnostics: _ } => false + Accepted { value: captured, diagnostics: _ } => + match find_named_child(root: captured, name: ^wall) { + Rejected { diagnostics: _ } => false + Accepted { value: wall_n, diagnostics: _ } => + match find_named_child(root: wall_n, name: ^same) { + Rejected { diagnostics: _ } => false + Accepted { value: members, diagnostics: _ } => + match find_named_child(root: members, name: ^rollup) { + Rejected { diagnostics: _ } => false + Accepted { value: rollup_member, diagnostics: _ } => + match find_arrow_body_child(root: rollup_member) { + Rejected { diagnostics: _ } => false + Accepted { value: body, diagnostics: _ } => + if node_is_call(node: body) { + match call_callee_target(node: body) { + Rejected { diagnostics: _ } => false + Accepted { value: callee, diagnostics: _ } => + match node_atom_identity_optional(node: callee) { + Present { value: id } => id == ^leaf + Absent => false + } + } + } else { + false + } + } + } + } + } + } + } + } +} diff --git a/src/v2/test/claim/declaring_identity_spelling/production_ingest_test.dag b/src/v2/test/claim/declaring_identity_spelling/production_ingest_test.dag index 92fb97915d4..45ed46fc50f 100644 --- a/src/v2/test/claim/declaring_identity_spelling/production_ingest_test.dag +++ b/src/v2/test/claim/declaring_identity_spelling/production_ingest_test.dag @@ -383,7 +383,7 @@ test fn an_unbound_type_position_name_is_unresolvable_today() -> Bool { } } -test fn a_callee_name_is_absent_from_the_census_today() -> Bool { +test fn a_callee_name_reaches_the_census_holds() -> Bool { match census_of( path: call_only_path, unit: ^xl3_call_only @@ -391,7 +391,7 @@ test fn a_callee_name_is_absent_from_the_census_today() -> Bool { Present { value: census } => census_unreadable_roots_empty(census: census) && authored_is(census: census, spelling: "LocalParam") - && !authored_is(census: census, spelling: "helper") + && authored_is(census: census, spelling: "helper") Absent => false } } diff --git a/src/v2/test/claim/namespace_xl0/call_argument_mention_survival_test.dag b/src/v2/test/claim/namespace_xl0/call_argument_mention_survival_test.dag index 3eb16fd6c8e..d4c718e7aff 100644 --- a/src/v2/test/claim/namespace_xl0/call_argument_mention_survival_test.dag +++ b/src/v2/test/claim/namespace_xl0/call_argument_mention_survival_test.dag @@ -244,10 +244,10 @@ test fn a_call_argument_mention_is_absent_from_the_denominator_today() -> Bool { !sites_mention(sites: fixture_sites(), spelling: "xl0_call_only") } -// MEASURED DEFICIENCY 2: the CALLEE name of a call does not reach the collector either, so the -// erasure is of the whole call expression rather than of its arguments alone. -test fn a_callee_name_is_absent_from_the_denominator_today() -> Bool { - !sites_mention(sites: fixture_sites(), spelling: "xl0_callee") +// The brace-body call lowering repair keeps the callee in the normalized tree, so the collector +// now sees it. The argument-only miss above is a separate class and stays asserted-absent. +test fn a_callee_name_reaches_the_denominator_holds() -> Bool { + sites_mention(sites: fixture_sites(), spelling: "xl0_callee") } // WHAT "Bool REACHES THE COLLECTOR" ACTUALLY MEASURES, AND IT IS NOT WHAT ITS FIRST NAME CLAIMED. diff --git a/src/v2/workflow/floor_pure_producer_share.dag b/src/v2/workflow/floor_pure_producer_share.dag index b9657a12944..886c205d29a 100644 --- a/src/v2/workflow/floor_pure_producer_share.dag +++ b/src/v2/workflow/floor_pure_producer_share.dag @@ -495,6 +495,7 @@ data floor_cross_claim_pure_producers_warm: List = [ "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.expression_bodied_literal_fn_parsed", "v2.test.parse.expression_bodied_fn_decl_parse.braced_fn_control_parsed", + "v2.test.claim.body_lowering_block_body_call.body_lowering_block_call_parsed", "v2.test.claim.namespace_xl0.call_argument_mention_survival.call_argument_fixture_normalized" ]