diff --git a/dag/gunbc/stage0_emit_model.dag b/dag/gunbc/stage0_emit_model.dag index fd42bc7df9f..61b1291e69e 100644 --- a/dag/gunbc/stage0_emit_model.dag +++ b/dag/gunbc/stage0_emit_model.dag @@ -56,6 +56,8 @@ data generated_stage0_files: List = [ "v1_compiler_compile.rs", "v1_compiler_compiler_tests_rust.rs", "v1_compiler_complexity.rs", + "v1_compiler_dag_collect.rs", + "v1_compiler_dag_collect_support.rs", "v1_compiler_effect_derivation.rs", "v1_compiler_emit.rs", "v1_compiler_emit_core_support.rs", diff --git a/dag/test/claim/stage0_emit_model_witness_test.dag b/dag/test/claim/stage0_emit_model_witness_test.dag index 6a50133124b..c42ddfb76e3 100644 --- a/dag/test/claim/stage0_emit_model_witness_test.dag +++ b/dag/test/claim/stage0_emit_model_witness_test.dag @@ -8,7 +8,7 @@ import gunbc.stage0_emit_model { } fn w_generated_roster_count() -> Bool { - generated_stage0_file_count() == 90 + generated_stage0_file_count() == 92 } fn w_hand_maintained_not_classified_generated() -> Bool { diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index c3a34953445..bac4aef7420 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -179,6 +179,16 @@ type VariantFoldState { collision_errors: List } +type VariantExportSurface { + arm_owners: Map + enum_items: Map +} + +type TypeNameExportFacts { + exporter_count: Int + canonical_binding: TypeBinding? +} + type InferScope { type_env: TypeEnv func_env: ResolvedFuncEnv @@ -6030,53 +6040,85 @@ fn build_local_variants(items: List, source_indices: Map, source_indices: Map) -> Node? { - match map_get(parent_index, module_path) { - Absent => none - Present { value: tm } => - let own = tm.items - |> filter(item => item.connective == Disj) - |> filter(item => item.children |> any(c => authored_name_at(source_indices: source_indices, node: c) == arm_name)) - |> first - match own { - Present { value: item } => Present { value: item } - Absent => - let reexport = module_imports(n: tm.module) - |> filter(imp => !import_is_all(n: imp)) - |> filter(imp => import_specific_names_at(n: imp, source_indices: source_indices) |> any(n => n == arm_name)) - |> first - match reexport { - Present { value: imp } => owner_of_exported_arm(module_path: authored_name_at(source_indices: source_indices, node: imp), arm_name: arm_name, parent_index: parent_index, source_indices: source_indices) - Absent => none - } - } +fn empty_variant_export_surface() -> VariantExportSurface { + VariantExportSurface { arm_owners: empty_map(), enum_items: empty_map() } +} + +fn add_coproduct_to_variant_surface(acc: VariantExportSurface, item: Node, source_indices: Map) -> VariantExportSurface { + let enum_items = map_insert(acc.enum_items, authored_name_at(source_indices: source_indices, node: item), item) + let arm_owners = fold(item.children, init: acc.arm_owners, f: (arms, child) => + map_insert(arms, authored_name_at(source_indices: source_indices, node: child), item) + ) + VariantExportSurface { arm_owners: arm_owners, enum_items: enum_items } +} + +fn collect_own_variant_export_surface(items: List, source_indices: Map) -> VariantExportSurface { + fold(items, init: empty_variant_export_surface(), f: (acc, item) => + let item_is_coproduct = item.connective == Disj + if item_is_coproduct { + add_coproduct_to_variant_surface(acc: acc, item: item, source_indices: source_indices) + } else { acc } + ) +} + +fn merge_name_from_proxy_surface(acc: VariantExportSurface, proxy: VariantExportSurface, name: String) -> VariantExportSurface { + let with_arm = match map_get(proxy.arm_owners, name) { + Present { value: owner } => VariantExportSurface { arm_owners: map_insert(acc.arm_owners, name, owner), enum_items: acc.enum_items } + Absent => acc + } + match map_get(proxy.enum_items, name) { + Present { value: item } => VariantExportSurface { arm_owners: with_arm.arm_owners, enum_items: map_insert(with_arm.enum_items, name, item) } + Absent => with_arm } } -fn exported_coproduct_item(module_path: String, enum_name: String, parent_index: Map, source_indices: Map) -> Node? { - match map_get(parent_index, module_path) { - Absent => none - Present { value: tm } => - let own = tm.items - |> filter(item => item.connective == Disj) - |> filter(item => authored_name_at(source_indices: source_indices, node: item) == enum_name) - |> first - match own { - Present { value: item } => Present { value: item } - Absent => - let reexport = module_imports(n: tm.module) - |> filter(imp => !import_is_all(n: imp)) - |> filter(imp => import_specific_names_at(n: imp, source_indices: source_indices) |> any(n => n == enum_name)) - |> first - match reexport { - Present { value: imp } => exported_coproduct_item(module_path: authored_name_at(source_indices: source_indices, node: imp), enum_name: enum_name, parent_index: parent_index, source_indices: source_indices) - Absent => none - } - } +fn reexport_variant_surface_fragment(imp: Node, surfaces: Map, source_indices: Map) -> VariantExportSurface { + let proxy_path = import_module_path_at(imp: imp, source_indices: source_indices) + match map_get(surfaces, proxy_path) { + Absent => empty_variant_export_surface() + Present { value: proxy } => + fold(import_specific_names_at(n: imp, source_indices: source_indices), init: empty_variant_export_surface(), f: (acc, name) => + merge_name_from_proxy_surface(acc: acc, proxy: proxy, name: name) + ) } } -fn build_imported_variants(resolved_imports: List, parent_index: Map, source_indices: Map, module_name: String, init: VariantFoldState) -> VariantFoldState { +fn merge_variant_surface_fragments(acc: VariantExportSurface, frag: VariantExportSurface) -> VariantExportSurface { + VariantExportSurface { + arm_owners: map_merge(acc.arm_owners, frag.arm_owners), + enum_items: map_merge(acc.enum_items, frag.enum_items) + } +} + +fn build_variant_export_surface(tm: TypedModule, surfaces: Map, source_indices: Map) -> VariantExportSurface { + let own = collect_own_variant_export_surface(items: tm.items, source_indices: source_indices) + let reexports = fold( + module_imports(n: tm.module) |> filter(imp => !import_is_all(n: imp)), + init: empty_variant_export_surface(), + f: (acc, imp) => + merge_variant_surface_fragments( + acc: acc, + frag: reexport_variant_surface_fragment(imp: imp, surfaces: surfaces, source_indices: source_indices) + ) + ) + VariantExportSurface { + arm_owners: map_merge(reexports.arm_owners, own.arm_owners), + enum_items: map_merge(reexports.enum_items, own.enum_items) + } +} + +fn bind_imported_name_from_surface(state: VariantFoldState, surface: VariantExportSurface, name: String, source_indices: Map, module_name: String) -> VariantFoldState { + let after_enum = match map_get(surface.enum_items, name) { + Present { value: item } => bind_coproduct_item_arms(state: state, item: item, source_indices: source_indices, module_name: module_name) + Absent => state + } + match map_get(surface.arm_owners, name) { + Present { value: owner } => insert_variant_owner_checked(state: after_enum, arm_name: name, owner: owner, source_indices: source_indices, module_name: module_name) + Absent => after_enum + } +} + +fn build_imported_variants(resolved_imports: List, parent_index: Map, variant_surfaces: Map, source_indices: Map, module_name: String, init: VariantFoldState) -> VariantFoldState { fold(resolved_imports, init: init, f: (acc, imp) => let with_glob = if imp.is_all { let source_items = match map_get(parent_index, imp.module_path) { @@ -6091,20 +6133,18 @@ fn build_imported_variants(resolved_imports: List, parent_index: ) } else { acc } fold(imp.specific_names, init: with_glob, f: (nacc, name) => - let after_enum = match exported_coproduct_item(module_path: imp.module_path, enum_name: name, parent_index: parent_index, source_indices: source_indices) { - Present { value: item } => bind_coproduct_item_arms(state: nacc, item: item, source_indices: source_indices, module_name: module_name) - Absent => nacc - } - match owner_of_exported_arm(module_path: imp.module_path, arm_name: name, parent_index: parent_index, source_indices: source_indices) { - Present { value: owner } => insert_variant_owner_checked(state: after_enum, arm_name: name, owner: owner, source_indices: source_indices, module_name: module_name) - Absent => after_enum + let surface = match map_get(variant_surfaces, imp.module_path) { + Present { value: s } => s + Absent => empty_variant_export_surface() } + bind_imported_name_from_surface(state: nacc, surface: surface, name: name, source_indices: source_indices, module_name: module_name) ) ) } fn build_module_context(contributions: List, parent_index: Map, + variant_surfaces: Map, resolved_imports: List, env: TypeEnv, module_name: String) -> ModuleContext { @@ -6127,6 +6167,7 @@ fn build_module_context(contributions: List, let variant_fold = build_imported_variants( resolved_imports: resolved_imports, parent_index: parent_index, + variant_surfaces: variant_surfaces, source_indices: env.source_indices, module_name: module_name, init: local_variant_fold @@ -6172,7 +6213,7 @@ type TypecheckModuleResult { diagnostics: List } -fn typecheck_module(resolved: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable) -> TypecheckModuleResult { +fn typecheck_module(resolved: ResolvedModule, parent_index: Map, variant_surfaces: Map, source_indices: Map, intern_table: InternTable) -> TypecheckModuleResult { let env_result = build_type_env(module: resolved, parent_index: parent_index, source_indices: source_indices, intern_table: intern_table) let env = env_result.env let env_cache = env_result.cache @@ -6201,6 +6242,7 @@ fn typecheck_module(resolved: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable) -> TypecheckModuleResult { + typecheck_module(resolved: resolved, parent_index: parent_index, variant_surfaces: empty_map(), source_indices: source_indices, intern_table: intern_table) +} + type EnvResolveResult { env: TypeEnv diagnostics: List @@ -6502,6 +6548,7 @@ fn typecheck(graph: ModuleGraph, source_indices: Map, inte remaining: graph.modules, modules: [], module_index: empty_map(), + variant_surfaces: empty_map(), item_registry: empty_map(), diag_chunks: [], source_indices: source_indices, @@ -6513,6 +6560,7 @@ fn typecheck_modules( remaining: List, modules: List, module_index: Map, + variant_surfaces: Map, item_registry: Map, diag_chunks: List>, source_indices: Map, @@ -6524,13 +6572,20 @@ fn typecheck_modules( TypedGraph { modules: modules, item_registry: expanded_registry, diagnostics: flat_map(diag_chunks, c => c) } Present { value: resolved } => let parent_result = collect_parent_envs(resolved: resolved, module_index: module_index, source_indices: source_indices) - let tc_result = typecheck_module(resolved: resolved, parent_index: module_index, source_indices: source_indices, intern_table: intern_table) + let tc_result = typecheck_module(resolved: resolved, parent_index: module_index, variant_surfaces: variant_surfaces, source_indices: source_indices, intern_table: intern_table) let typed = tc_result.typed let tc_diags = tc_result.diagnostics + let typed_path = authored_name_at(source_indices: source_indices, node: typed.module) + let variant_surfaces = map_insert( + variant_surfaces, + typed_path, + build_variant_export_surface(tm: typed, surfaces: variant_surfaces, source_indices: source_indices) + ) typecheck_modules( remaining: remaining |> skip(1), modules: list_push(modules, typed), - module_index: map_insert(module_index, authored_name_at(source_indices: source_indices, node: typed.module), typed), + module_index: map_insert(module_index, typed_path, typed), + variant_surfaces: variant_surfaces, item_registry: map_merge(item_registry, typed.item_registry), diag_chunks: list_push(list_push(diag_chunks, parent_result.diagnostics), tc_diags), source_indices: source_indices, @@ -6548,24 +6603,38 @@ fn import_module_path_at(imp: Node, source_indices: Map) - authored_name_at(source_indices: source_indices, node: imp) } -fn local_authority_binding(owner: TypedModule, name: String) -> TypeBinding? { - match filter(owner.type_env.bindings |> map_values, b => b.name == name) |> first { - Present { value: binding } => Present { value: binding } - Absent => Absent - } +type ExportIndexModuleAccum { + index: Map + seen_names: Set } -fn global_type_exporter_count(modules: List, name: String) -> Int { - filter(modules, m => module_exports_type_name(m: m, name: name)) |> count +fn export_index_merge_module(acc: Map, m: TypedModule) -> Map { + fold(m.type_env.bindings |> map_values, init: ExportIndexModuleAccum { index: acc, seen_names: empty_set() }, f: (state, binding) => + let name = binding.name + if set_contains(state.seen_names, name) { + state + } else { + let canonical = match filter(m.type_env.bindings |> map_values, b => b.name == name) |> first { + Present { value: b } => b + Absent => binding + } + let seen_names = set_insert(state.seen_names, name) + let index = match map_get(state.index, name) { + Absent => + map_insert(state.index, name, TypeNameExportFacts { exporter_count: 1, canonical_binding: Present { value: canonical } }) + Present { value: facts } => + map_insert(state.index, name, TypeNameExportFacts { + exporter_count: facts.exporter_count + 1, + canonical_binding: Present { value: canonical } + }) + } + ExportIndexModuleAccum { index: index, seen_names: seen_names } + } + ).index } -data local_authority_for_name_dissolution_trigger: String = "๐ŸŸก dissolve-on: single-exporter rewire canonical picks last TypedModule in compilation list (filter then last); multi-exporter names skip rewire and defer to merge_type_env_cache overlay-wins โ€” ground on import-order authority at v2 regen." - -fn local_authority_for_name(modules: List, name: String) -> TypedModule? { - match filter(modules, m => (filter(m.type_env.bindings |> map_values, b => b.name == name) |> count) > 0) |> last { - Present { value: owner } => Present { value: owner } - Absent => Absent - } +fn build_type_name_export_index(modules: List) -> Map { + fold(modules, init: empty_map(), f: (acc, m) => export_index_merge_module(acc: acc, m: m)) } fn module_exports_type_name(m: TypedModule, name: String) -> Bool { @@ -6582,8 +6651,10 @@ fn direct_import_exporter_count(m: TypedModule, name: String, index: Map count } +data local_authority_for_name_dissolution_trigger: String = "๐ŸŸก dissolve-on: single-exporter rewire canonical picks last TypedModule in compilation list (filter then last); multi-exporter names skip rewire and defer to merge_type_env_cache overlay-wins โ€” ground on import-order authority at v2 regen." + fn rewire_inherited_str_binding( - modules: List, + type_name_index: Map, module_index: Map, consumer: TypedModule, source_indices: Map, @@ -6592,12 +6663,16 @@ fn rewire_inherited_str_binding( ancestry_str_bindings: Map, name: String ) -> StrBindingsRewireAccum { - if set_contains(local_names, name) || direct_import_exporter_count(m: consumer, name: name, index: module_index, source_indices: source_indices) > 1 || global_type_exporter_count(modules: modules, name: name) > 1 { + let exporter_count = match map_get(type_name_index, name) { + Present { value: facts } => facts.exporter_count + Absent => 0 + } + if set_contains(local_names, name) || direct_import_exporter_count(m: consumer, name: name, index: module_index, source_indices: source_indices) > 1 || exporter_count > 1 { StrBindingsRewireAccum { str_bindings: str_bindings, ancestry_str_bindings: ancestry_str_bindings } } else { - match local_authority_for_name(modules: modules, name: name) { - Present { value: owner } => - match local_authority_binding(owner: owner, name: name) { + match map_get(type_name_index, name) { + Present { value: facts } => + match facts.canonical_binding { Present { value: canonical } => let ancestry_str_bindings = map_insert(ancestry_str_bindings, name, canonical) let str_bindings = match map_get(str_bindings, name) { @@ -6613,6 +6688,7 @@ fn rewire_inherited_str_binding( } fn rewire_type_env_import_str_binding_identity(modules: List, source_indices: Map) -> List { + let type_name_index = build_type_name_export_index(modules: modules) let index = fold(modules, init: empty_map(), f: (acc, m) => map_insert(acc, authored_name_at(source_indices: source_indices, node: m.module), m) ) @@ -6623,7 +6699,7 @@ fn rewire_type_env_import_str_binding_identity(modules: List, sourc let inherited_keys = concat(ancestry_keys, str_keys) let rewired = fold(inherited_keys, init: StrBindingsRewireAccum { str_bindings: m.type_env.str_bindings, ancestry_str_bindings: m.type_env.ancestry_str_bindings }, f: (acc, name) => rewire_inherited_str_binding( - modules: modules, + type_name_index: type_name_index, module_index: index, consumer: m, source_indices: source_indices, diff --git a/src/v1/04_method.dag b/src/v1/04_method.dag index cb5cd096453..41c8bc86730 100644 --- a/src/v1/04_method.dag +++ b/src/v1/04_method.dag @@ -87,8 +87,8 @@ fn builtin_function_registry() -> Map { let m = map_insert(m, "Some", with_optional_cardinality(n: type_variable_node(id: "some_inner"))) let m = map_insert(m, "map_keys", list_of_type_variable(id: "collection_element")) let m = map_insert(m, "map_values", list_of_type_variable(id: "collection_element")) - let m = map_insert(m, "reverse", list_of_type_variable(id: "collection_element")) let m = map_insert(m, "get", with_optional_cardinality(n: type_variable_node(id: "collection_element"))) + let m = map_insert(m, "reverse", list_of_type_variable(id: "collection_element")) let m = map_insert(m, "list_push", list_of_type_variable(id: "collection_element")) let m = map_insert(m, "hash_combine", hash_type) let m = map_insert(m, "atom_identity_hash", hash_type) diff --git a/src/v1/04_resolve.dag b/src/v1/04_resolve.dag index 08894254be4..39486314863 100644 --- a/src/v1/04_resolve.dag +++ b/src/v1/04_resolve.dag @@ -230,47 +230,33 @@ fn resolve_node(n: Node, env: TypeEnv, module_name: String) -> NodeResolveResult data parameterized_use_site_prefers_parameterized_decl: String = "A use site WITH type arguments (children > 0) whose primary name lookup lands on a PARAMLESS decl is definitionally mis-resolved - type args imply a generic declaration. The concrete seam: the kernel prelude binding for a name (e.g. the kernel Optional) shadows an explicitly imported user generic of the same name in the ancestry rung, so v2's Optional consumers resolved the paramless kernel decl, is_user_generic_use_site went false, the sig return was never expanded, and pattern bindings went error-typed with zero diagnostics (the interpreted-parse cascade; pre-existing on main). Retry the DIRECT import parents (str_bindings) for a parameterized decl before accepting the paramless one - the params filter naturally excludes the kernel parent, and the ancestry rung itself carries the kernel-shadowed entry so it cannot serve as the retry source." -fn lookup_ancestry_parameterized_decl(env: TypeEnv, brand: String) -> Node? { - match env.parents |> filter(parent => - match map_get(parent.str_bindings, brand) { - Present { value: b } => b.resolved.params |> count > 0 - Absent => false - } - ) |> first { - Present { value: parent } => - match map_get(parent.str_bindings, brand) { - Present { value: b } => Present { value: b.resolved } - Absent => Absent - } - Absent => Absent - } -} - -fn resolve_paramless_generic_use_decl(env: TypeEnv, n: Node, decl: Node, brand: String) -> Node { - match n.children |> count > 0 { - true => - match lookup_ancestry_parameterized_decl(env: env, brand: brand) { - Present { value: user_decl } => user_decl - Absent => - match n.name != "" && n.name != brand { - true => - match lookup_type_by_name(env: env, name: n.name) { - Present { value: structural } => structural - Absent => decl - } - false => decl - } - } - false => decl - } -} - fn resolve_generic_use_decl(env: TypeEnv, n: Node) -> Node { let brand = authored_name(env: env, node: n) match lookup_type_for(env: env, node: n) { Present { value: decl } => if decl.params |> count > 0 { decl } - else { resolve_paramless_generic_use_decl(env: env, n: n, decl: decl, brand: brand) } + else if n.children |> count > 0 { + let ancestry_generic = fold(env.parents, init: none, f: (acc, parent) => + if acc != none { acc } + else { + match map_get(parent.str_bindings, brand) { + Present { value: b } => + if b.resolved.params |> count > 0 { Present { value: b.resolved } } else { acc } + Absent => acc + } + } + ) + match ancestry_generic { + Present { value: user_decl } => user_decl + Absent => + if n.name != "" && n.name != brand { + match lookup_type_by_name(env: env, name: n.name) { + Present { value: structural } => structural + Absent => decl + } + } else { decl } + } + } else { decl } Absent => match lookup_type_by_name(env: env, name: n.name) { Present { value: decl } => decl diff --git a/src/v1/compiler_tests_rust.dag b/src/v1/compiler_tests_rust.dag index 9bcf3950f13..ae89c65bceb 100644 --- a/src/v1/compiler_tests_rust.dag +++ b/src/v1/compiler_tests_rust.dag @@ -1125,7 +1125,11 @@ fn ct_profile_reconcile_test() -> String { " );\n", " acc\n", " },\n", - " ));\n\n", + " ));\n", + " let mut variant_surfaces = crate::v1_rt::rc_empty_map::<\n", + " String,\n", + " std::rc::Rc,\n", + " >();\n\n", " for resolved in graph.modules.iter() {\n", " let name = resolved.module.name.to_string();\n", " let item_count =\n", @@ -1193,6 +1197,7 @@ fn ct_profile_reconcile_test() -> String { " let tc_result = crate::v1_compiler_infer::typecheck_module(\n", " resolved.clone(),\n", " module_index.clone(),\n", + " variant_surfaces.clone(),\n", " source_indices.clone(),\n", " intern_table.clone(),\n", " );\n", @@ -1222,6 +1227,19 @@ fn ct_profile_reconcile_test() -> String { " panic!(\"ABORT: '{}' took {:?} (>10s)\", name, full_elapsed);\n", " }\n\n", " let typed = tc_result.typed.clone();\n", + " let typed_path = crate::v1_std_core::authored_name_at(\n", + " source_indices.clone(),\n", + " typed.module.clone(),\n", + " );\n", + " variant_surfaces = crate::v1_rt::rc_map_insert(\n", + " variant_surfaces.clone(),\n", + " typed_path,\n", + " crate::v1_compiler_infer::build_variant_export_surface(\n", + " typed.clone(),\n", + " variant_surfaces.clone(),\n", + " source_indices.clone(),\n", + " ),\n", + " );\n", " mi_raw.insert(name, typed);\n", " }\n\n", " let rss_final = get_rss_bytes();\n", diff --git a/src/v1/dag_collect.dag b/src/v1/dag_collect.dag index 13fbdad22fb..6254c79e3ed 100644 --- a/src/v1/dag_collect.dag +++ b/src/v1/dag_collect.dag @@ -9,9 +9,8 @@ import v1.std.core { import_is_all } import v1.compiler.dag_collect_support { - DagCollectAcc, DagCollectSlot, - dag_collect_fp_memo_reset, dag_collect_pack_slots, dag_collect_slot_seq, - dag_node_surface_fingerprint, dag_node_surface_fingerprint_memo + DagCollectAcc, DagCollectPending, + dag_node_surface_fingerprint, dag_node_key_collision_error } import v1.compiler.infer_items { ResolvedGraph, TypedModule } @@ -53,7 +52,7 @@ fn dag_node_collection_anchor(node: Node) -> Node { fn dag_node_key(node: Node) -> String { let anchor = dag_node_collection_anchor(node: node) if anchor.span.start == 0 && anchor.span.end == 0 { - concat(":0..0:", dag_node_surface_fingerprint_memo(node: anchor)) + concat(":0..0:", dag_node_surface_fingerprint(node: anchor)) } else { concat( anchor.span.file, ":", to_string(i: anchor.span.start), "..", to_string(i: anchor.span.end), @@ -65,90 +64,92 @@ fn dag_node_key(node: Node) -> String { } fn dag_node_fingerprint(node: Node) -> String { - dag_node_surface_fingerprint_memo(node: dag_node_collection_anchor(node: node)) + dag_node_surface_fingerprint(node: dag_node_collection_anchor(node: node)) } -fn dag_collect_nodes_list(nodes: List, slots: Map, collision_errors: List) -> Map { - nodes |> fold(init: slots, f: (s, n) => dag_collect_insert_slots(node: n, slots: s, collision_errors: collision_errors)) +fn dag_collect_nodes_list(nodes: List, acc: DagCollectAcc) -> DagCollectAcc { + nodes |> fold(init: acc, f: (a, n) => dag_collect_insert(node: n, acc: a)) } -fn dag_collect_optional_node(value: Node?, slots: Map, collision_errors: List) -> Map { +fn dag_collect_optional_node(value: Node?, acc: DagCollectAcc) -> DagCollectAcc { match value { - Present { value: inner } => dag_collect_insert_slots(node: inner, slots: slots, collision_errors: collision_errors) - Absent => slots + Present { value: inner } => dag_collect_insert(node: inner, acc: acc) + Absent => acc } } -fn dag_collect_inferred(value: InferredNode?, slots: Map, collision_errors: List) -> Map { +fn dag_collect_inferred(value: InferredNode?, acc: DagCollectAcc) -> DagCollectAcc { match value { - Present { value: Resolved { node: n } } => dag_collect_insert_slots(node: n, slots: slots, collision_errors: collision_errors) - _ => slots + Present { value: Resolved { node: n } } => dag_collect_insert(node: n, acc: acc) + _ => acc } } -fn dag_collect_match_pattern(pattern: MatchPattern, slots: Map, collision_errors: List) -> Map { +fn dag_collect_match_pattern(pattern: MatchPattern, acc: DagCollectAcc) -> DagCollectAcc { match pattern { - Bind { name: _ } => slots - LitPattern { value: _ } => slots + Bind { name: _ } => acc + LitPattern { value: _ } => acc VariantPattern { name: _, parent_enum: _, field_bindings: field_bindings } => - field_bindings |> fold(init: slots, f: (s, fb) => dag_collect_insert_slots(node: fb, slots: s, collision_errors: collision_errors)) - Wildcard => slots + field_bindings |> fold(init: acc, f: (a, fb) => dag_collect_insert(node: fb, acc: a)) + Wildcard => acc } } -fn dag_collect_node_tree(node: Node, slots: Map, collision_errors: List) -> Map { - let slots = dag_collect_nodes_list(nodes: node.children, slots: slots, collision_errors: collision_errors) - let slots = if is_module_shell_node(n: node) { - slots +fn dag_collect_node_tree(node: Node, acc: DagCollectAcc) -> DagCollectAcc { + let acc = dag_collect_nodes_list(nodes: node.children, acc: acc) + let acc = if is_module_shell_node(n: node) { + acc } else { - dag_collect_nodes_list(nodes: node.params, slots: slots, collision_errors: collision_errors) + dag_collect_nodes_list(nodes: node.params, acc: acc) } - let slots = dag_collect_nodes_list(nodes: node.uses, slots: slots, collision_errors: collision_errors) - let slots = dag_collect_optional_node(value: node.body, slots: slots, collision_errors: collision_errors) - let slots = dag_collect_optional_node(value: node.transport, slots: slots, collision_errors: collision_errors) - let slots = dag_collect_nodes_list(nodes: node.properties, slots: slots, collision_errors: collision_errors) - let slots = dag_collect_optional_node(value: node.type_annotation, slots: slots, collision_errors: collision_errors) - let slots = dag_collect_inferred(value: node.inferred, slots: slots, collision_errors: collision_errors) + let acc = dag_collect_nodes_list(nodes: node.uses, acc: acc) + let acc = dag_collect_optional_node(value: node.body, acc: acc) + let acc = dag_collect_optional_node(value: node.transport, acc: acc) + let acc = dag_collect_nodes_list(nodes: node.properties, acc: acc) + let acc = dag_collect_optional_node(value: node.type_annotation, acc: acc) + let acc = dag_collect_inferred(value: node.inferred, acc: acc) match node.match_pattern { - Present { value: p } => dag_collect_match_pattern(pattern: p, slots: slots, collision_errors: collision_errors) - Absent => slots + Present { value: p } => dag_collect_match_pattern(pattern: p, acc: acc) + Absent => acc } } data dag_collect_synthetic_key_note: String = "Synthetic span-0 keys embed the fingerprint; the Absent branch recovers it by substring and must not re-hash." -fn dag_collect_insert_slots(node: Node, slots: Map, collision_errors: List) -> Map { +fn dag_collect_insert(node: Node, acc: DagCollectAcc) -> DagCollectAcc { let anchor = dag_node_collection_anchor(node: node) let key = dag_node_key(node: anchor) - match map_get(slots, key) { - Present { value: _ } => slots + match map_get(acc.seen, key) { + Present { value: _ } => acc Absent => { let fp = if anchor.span.start == 0 && anchor.span.end == 0 { substring(s: key, start: 6, end: string_length(s: key)) } else { dag_node_fingerprint(node: anchor) } - let seq = dag_collect_slot_seq(slots: slots) - dag_collect_node_tree( - node: anchor, - slots: map_insert(slots, key, DagCollectSlot { key: key, fp: fp, node: anchor, seq: seq }), - collision_errors: collision_errors - ) + [DagCollectPending { anchor: anchor, key: key, fp: fp }] + |> fold( + init: acc, + f: (inner, pending) => + dag_collect_node_tree( + node: pending.anchor, + acc: DagCollectAcc { + seen: map_insert(inner.seen, pending.key, pending.fp), + order: list_push(inner.order, pending.anchor), + collision_errors: inner.collision_errors + } + ) + ) } } } -fn dag_collect_from_module(module: TypedModule, slots: Map, collision_errors: List) -> Map { - let slots = dag_collect_insert_slots(node: module.module, slots: slots, collision_errors: collision_errors) - module.items |> fold(init: slots, f: (s, item) => dag_collect_insert_slots(node: item, slots: s, collision_errors: collision_errors)) +fn dag_collect_from_module(module: TypedModule, acc: DagCollectAcc) -> DagCollectAcc { + let acc = dag_collect_insert(node: module.module, acc: acc) + module.items |> fold(init: acc, f: (a, item) => dag_collect_insert(node: item, acc: a)) } -data dag_collect_collision_threading_dissolve_on: String = "Dissolve-on: drop collision_errors threading once synthetic-key construction proof is a typed wall (key embeds fp โ‡’ key-equal โ‡’ fp-equal); kept for emit_dag gate shape." - fn collect_dag_nodes(typed: ResolvedGraph) -> DagCollectAcc { - let _memo_reset = dag_collect_fp_memo_reset() - let collision_errors = [] - let slots = typed.modules - |> fold(init: empty_map(), f: (s, m) => dag_collect_from_module(module: m, slots: s, collision_errors: collision_errors)) - dag_collect_pack_slots(slots: slots, collision_errors: collision_errors) + typed.modules + |> fold(init: DagCollectAcc { seen: empty_map(), order: [], collision_errors: [] }, f: (acc, m) => dag_collect_from_module(module: m, acc: acc)) } diff --git a/src/v1/dag_collect_support.dag b/src/v1/dag_collect_support.dag index 0bfcab243d3..9b30ca3fe16 100644 --- a/src/v1/dag_collect_support.dag +++ b/src/v1/dag_collect_support.dag @@ -9,28 +9,16 @@ import v1.std.core { } import v1.compiler.emit { escape_json_string } -type DagCollectSlot { - key: String - fp: String - node: Node - seq: Int -} - type DagCollectAcc { seen: Map order: List collision_errors: List } -fn dag_collect_slot_seq(slots: Map) -> Int { - slots |> count -} - -fn dag_collect_pack_slots(slots: Map, collision_errors: List) -> DagCollectAcc { - let ordered_slots = slots |> map_values |> sort_by(s => s.seq) - let order = ordered_slots |> map(s => s.node) - let seen = ordered_slots |> fold(init: empty_map(), f: (acc, s) => map_insert(acc, s.key, s.fp)) - DagCollectAcc { seen: seen, order: order, collision_errors: collision_errors } +type DagCollectPending { + anchor: Node + key: String + fp: String } fn json_quote(s: String) -> String { @@ -115,7 +103,7 @@ fn dag_node_surface_fingerprint_rec(node: Node) -> String { } fn dag_node_surface_fingerprint(node: Node) -> String { - dag_node_surface_fingerprint_rec(node: node) + dag_node_surface_fingerprint_memo(node: node) } data dag_collect_fp_memo_dissolve_on: String = "Dissolve-on: ground on std.content_hash + node-keyed memo table once v2 adoption clears for this stage. Realization threads a per-collect memo reset at collect_dag_nodes entry; Rust seed uses Rc::as_ptr until then." @@ -125,7 +113,7 @@ fn dag_collect_fp_memo_reset() -> Bool { } fn dag_node_surface_fingerprint_memo(node: Node) -> String { - dag_node_surface_fingerprint(node: node) + dag_node_surface_fingerprint_rec(node: node) } fn dag_node_key_collision_error(key: String, span: SourceSpan) -> ErrorNode { diff --git a/src/v1/stage0/src/bin/regen_stage0.rs b/src/v1/stage0/src/bin/regen_stage0.rs index 60ab5a5e5c8..d584dd5169f 100644 --- a/src/v1/stage0/src/bin/regen_stage0.rs +++ b/src/v1/stage0/src/bin/regen_stage0.rs @@ -73,6 +73,8 @@ const GENERATED_STAGE0_FILES: &[&str] = &[ "v1_compiler_compile.rs", "v1_compiler_compiler_tests_rust.rs", "v1_compiler_complexity.rs", + "v1_compiler_dag_collect.rs", + "v1_compiler_dag_collect_support.rs", "v1_compiler_effect_derivation.rs", "v1_compiler_emit.rs", "v1_compiler_emit_core_support.rs", @@ -127,12 +129,6 @@ const HAND_MAINTAINED_STAGE0_FILES: &[&str] = &[ // Phase-local heartbeat for long floor walks (2026-07-04); its own header // carries the dissolution trigger (realization_measurement_loop Phase 0). "phase_profile.rs", - // P1 dag_collect perf (#6242): slot-map + fp/key memo live only in hand-edited seed until - // regen is unblocked (04_resolve.dag) and std.content_hash memo rows emit. Regen without - // this guard reverted to DagCollectPending / O(Mยฒ) three times (90ad8fa, 42c8f3d, โ€ฆ). - // Dissolution: regen_stage0 --verify green with .dag model = emitted seed; drop from here. - "v1_compiler_dag_collect.rs", - "v1_compiler_dag_collect_support.rs", ]; /// Hand-maintained stage0 support living in subdirectories (not flat `.rs` files). diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 1a0ef765766..3021eb969ff 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -1270,6 +1270,8 @@ fn reconcile_with_typed_cache( let mut module_index: Rc>> = v1_rt::rc_empty_map(); let mut item_registry: Rc>> = v1_rt::rc_empty_map(); let mut diag_chunks: Vec>>> = Vec::new(); + let mut variant_surfaces: Rc>> = + v1_rt::rc_empty_map(); for resolved in graph.modules.iter().cloned() { let parent_result = v1_compiler_infer::collect_parent_envs( @@ -1304,6 +1306,7 @@ fn reconcile_with_typed_cache( let computed = v1_compiler_infer::typecheck_module( resolved.clone(), module_index.clone(), + variant_surfaces.clone(), source_indices.clone(), intern_table.clone(), ); @@ -1323,11 +1326,17 @@ fn reconcile_with_typed_cache( }; let typed = tc_result.typed.clone(); modules = v1_rt::rc_list_push(modules, typed.clone()); - module_index = v1_rt::rc_map_insert( - module_index, - authored_name_at(source_indices.clone(), typed.module.clone()), - typed.clone(), + let typed_path = authored_name_at(source_indices.clone(), typed.module.clone()); + variant_surfaces = v1_rt::rc_map_insert( + variant_surfaces.clone(), + typed_path.clone(), + v1_compiler_infer::build_variant_export_surface( + typed.clone(), + variant_surfaces.clone(), + source_indices.clone(), + ), ); + module_index = v1_rt::rc_map_insert(module_index, typed_path, typed.clone()); item_registry = v1_rt::rc_map_merge(item_registry, typed.item_registry.clone()); diag_chunks.push(parent_result.diagnostics.clone()); diag_chunks.push(tc_result.diagnostics.clone()); diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 4728261cd2d..91657246e79 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -1640,6 +1640,10 @@ mod compiler_tests { acc }, )); + let mut variant_surfaces = crate::v1_rt::rc_empty_map::< + String, + std::rc::Rc, + >(); for resolved in graph.modules.iter() { let name = resolved.module.name.to_string(); @@ -1717,6 +1721,7 @@ mod compiler_tests { let tc_result = crate::v1_compiler_infer::typecheck_module( resolved.clone(), module_index.clone(), + variant_surfaces.clone(), source_indices.clone(), intern_table.clone(), ); @@ -1749,6 +1754,19 @@ mod compiler_tests { } let typed = tc_result.typed.clone(); + let typed_path = crate::v1_std_core::authored_name_at( + source_indices.clone(), + typed.module.clone(), + ); + variant_surfaces = crate::v1_rt::rc_map_insert( + variant_surfaces.clone(), + typed_path, + crate::v1_compiler_infer::build_variant_export_surface( + typed.clone(), + variant_surfaces.clone(), + source_indices.clone(), + ), + ); mi_raw.insert(name, typed); } 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 b812888d2d5..fe84230b1bd 100644 --- a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs +++ b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs @@ -283,7 +283,224 @@ pub fn ct_profile_full_pipeline_test() -> String { } pub fn ct_profile_reconcile_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(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(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(), " #[ignore]\n".to_string()), " fn profile_reconcile_per_module() {\n".to_string()), " let result = std::thread::Builder::new()\n".to_string()), " .stack_size(64 * 1024 * 1024)\n".to_string()), " .spawn(|| {\n".to_string()), " use std::collections::HashMap;\n".to_string()), " use std::time::Instant;\n\n".to_string()), " let sources = self_compile_sources();\n\n".to_string()), " eprintln!(\n".to_string()), " \"\\n=== PER-MODULE RECONCILE PROFILE ({} sources) ===\",\n".to_string()), " sources.len()\n".to_string()), " );\n\n".to_string()), " let t0 = Instant::now();\n".to_string()), " let mut modules = Vec::new();\n".to_string()), " let mut intern_table = crate::v1_std_core::empty_intern_table();\n".to_string()), " for source in &sources {\n".to_string()), " let tokens = crate::v1_compiler_tokenize::tokenize(\n".to_string()), " source.content.clone(),\n".to_string()), " source.path.clone(),\n".to_string()), " );\n".to_string()), " let si = crate::v1_std_core::build_newline_index(\n".to_string()), " source.path.clone(),\n".to_string()), " source.content.clone(),\n".to_string()), " );\n".to_string()), " let parsed = crate::v1_compiler_parse::parse_with_table(\n".to_string()), " tokens.clone(),\n".to_string()), " crate::v1_rt::rc_map_insert(\n".to_string()), " crate::v1_rt::rc_empty_map::>(),\n".to_string()), " si.file.clone(),\n".to_string()), " si.clone(),\n".to_string()), " ),\n".to_string()), " intern_table.clone(),\n".to_string()), " );\n".to_string()), " let result = parsed.result.clone();\n".to_string()), " intern_table = parsed.intern_table.clone();\n".to_string()), " let m = result.module.clone()\n".to_string()), " .unwrap_or_else(|| panic!(\"parse failed for source {}\", source.path));\n".to_string()), " modules.push(m);\n".to_string()), " }\n".to_string()), " let resolve_si = sources.iter().fold(\n".to_string()), " crate::v1_rt::rc_empty_map::>(),\n".to_string()), " |acc, s| crate::v1_rt::rc_map_insert(acc, s.path.clone(), crate::v1_std_core::build_newline_index(s.path.clone(), s.content.clone())),\n".to_string()), " );\n".to_string()), " let graph = crate::v1_compiler_resolve::resolve_modules(std::rc::Rc::new(modules), resolve_si);\n".to_string()), " let setup_time = t0.elapsed();\n".to_string()), " let rss_baseline = get_rss_bytes();\n".to_string()), " eprintln!(\n".to_string()), " \" Setup (tok+parse+resolve): {:?} | RSS: {}\",\n".to_string()), " setup_time,\n".to_string()), " format_bytes(rss_baseline)\n".to_string()), " );\n".to_string()), " eprintln!(\" Modules to reconcile: {}\\n\", graph.modules.len());\n\n".to_string()), " let mut mi_raw = HashMap::<\n".to_string()), " String,\n".to_string()), " std::rc::Rc,\n".to_string()), " >::new();\n".to_string()), " let source_indices = std::rc::Rc::new(sources.iter().fold(\n".to_string()), " HashMap::>::new(),\n".to_string()), " |mut acc, source| {\n".to_string()), " acc.insert(\n".to_string()), " source.path.clone(),\n".to_string()), " crate::v1_std_core::build_newline_index(\n".to_string()), " source.path.clone(),\n".to_string()), " source.content.clone(),\n".to_string()), " ),\n".to_string()), " );\n".to_string()), " acc\n".to_string()), " },\n".to_string()), " ));\n\n".to_string()), " for resolved in graph.modules.iter() {\n".to_string()), " let name = resolved.module.name.to_string();\n".to_string()), " let item_count =\n".to_string()), " crate::v1_std_core::module_items(resolved.module.clone()).len();\n".to_string()), " let rss_before = get_rss_bytes();\n\n".to_string()), " eprint!(\" {:>35} ({:>3} items) ... \", name, item_count);\n\n".to_string()), " let module_index = std::rc::Rc::new(mi_raw.clone());\n\n".to_string()), " let t_unres = Instant::now();\n".to_string()), " let _unres = crate::v1_compiler_infer::build_type_env_unresolved(\n".to_string()), " resolved.clone(),\n".to_string()), " module_index.clone(),\n".to_string()), " source_indices.clone(),\n".to_string()), " intern_table.clone(),\n".to_string()), " );\n".to_string()), " let unres_elapsed = t_unres.elapsed();\n".to_string()), " let rss_after_unres = get_rss_bytes();\n".to_string()), " let unres_delta = rss_after_unres.saturating_sub(rss_before);\n\n".to_string()), " eprint!(\n".to_string()), " \"cycles={:>8.2?}(+{}) \",\n".to_string()), " unres_elapsed,\n".to_string()), " format_bytes(unres_delta)\n".to_string()), " );\n\n".to_string()), " if unres_delta > 256 * 1024 * 1024 {\n".to_string()), " eprintln!(\"\");\n".to_string()), " panic!(\n".to_string()), " \"ABORT: '{}' cycle detection grew RSS by {}\",\n".to_string()), " name,\n".to_string()), " format_bytes(unres_delta)\n".to_string()), " );\n".to_string()), " }\n\n".to_string()), " let t_env = Instant::now();\n".to_string()), " let env_result = crate::v1_compiler_infer::build_type_env(\n".to_string()), " resolved.clone(),\n".to_string()), " module_index.clone(),\n".to_string()), " source_indices.clone(),\n".to_string()), " intern_table.clone(),\n".to_string()), " );\n".to_string()), " let env_elapsed = t_env.elapsed();\n".to_string()), " let rss_after_env = get_rss_bytes();\n".to_string()), " let env_delta = rss_after_env.saturating_sub(rss_before);\n".to_string()), " let env_errs: usize = env_result\n".to_string()), " .diagnostics\n".to_string()), " .iter()\n".to_string()), " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone()))\n".to_string()), " .count();\n\n".to_string()), " eprint!(\n".to_string()), " \"env={:>8.2?}(+{},e={}) \",\n".to_string()), " env_elapsed,\n".to_string()), " format_bytes(env_delta),\n".to_string()), " env_errs\n".to_string()), " );\n\n".to_string()), " if env_delta > 512 * 1024 * 1024 {\n".to_string()), " eprintln!(\"\");\n".to_string()), " panic!(\n".to_string()), " \"ABORT: '{}' build_type_env grew RSS by {}\",\n".to_string()), " name,\n".to_string()), " format_bytes(env_delta)\n".to_string()), " );\n".to_string()), " }\n".to_string()), " if env_elapsed.as_secs() > 10 {\n".to_string()), " eprintln!(\"\");\n".to_string()), " panic!(\"ABORT: '{}' build_type_env took {:?}\", name, env_elapsed);\n".to_string()), " }\n\n".to_string()), " let t_full = Instant::now();\n".to_string()), " let tc_result = crate::v1_compiler_infer::typecheck_module(\n".to_string()), " resolved.clone(),\n".to_string()), " module_index.clone(),\n".to_string()), " source_indices.clone(),\n".to_string()), " intern_table.clone(),\n".to_string()), " );\n".to_string()), " let full_elapsed = t_full.elapsed();\n".to_string()), " let rss_after = get_rss_bytes();\n".to_string()), " let delta = rss_after.saturating_sub(rss_before);\n".to_string()), " let diag_count: usize = tc_result\n".to_string()), " .diagnostics\n".to_string()), " .iter()\n".to_string()), " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone()))\n".to_string()), " .count();\n\n".to_string()), " eprintln!(\n".to_string()), " \"full={:>8.2?} | RSS: {} (+{}) | errs: {}\",\n".to_string()), " full_elapsed,\n".to_string()), " format_bytes(rss_after),\n".to_string()), " format_bytes(delta),\n".to_string()), " diag_count\n".to_string()), " );\n\n".to_string()), " if delta > 512 * 1024 * 1024 {\n".to_string()), " panic!(\n".to_string()), " \"ABORT: '{}' grew RSS by {} (>512MB)\",\n".to_string()), " name,\n".to_string()), " format_bytes(delta)\n".to_string()), " );\n".to_string()), " }\n".to_string()), " if full_elapsed.as_secs() > 10 {\n".to_string()), " panic!(\"ABORT: '{}' took {:?} (>10s)\", name, full_elapsed);\n".to_string()), " }\n\n".to_string()), " let typed = tc_result.typed.clone();\n".to_string()), " mi_raw.insert(name, typed);\n".to_string()), " }\n\n".to_string()), " let rss_final = get_rss_bytes();\n".to_string()), " eprintln!(\n".to_string()), " \"\\n RSS final: {} (from baseline: +{})\",\n".to_string()), " format_bytes(rss_final),\n".to_string()), " format_bytes(rss_final.saturating_sub(rss_baseline))\n".to_string()), " );\n".to_string()), " eprintln!(\"=== DONE ===\\n\");\n".to_string()), " })\n".to_string()), " .expect(\"failed to spawn thread\")\n".to_string()), " .join();\n".to_string()), " result.expect(\"profile_reconcile_per_module 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(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(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] +".to_string(), " #[ignore] +".to_string()), " fn profile_reconcile_per_module() { +".to_string()), " let result = std::thread::Builder::new() +".to_string()), " .stack_size(64 * 1024 * 1024) +".to_string()), " .spawn(|| { +".to_string()), " use std::collections::HashMap; +".to_string()), " use std::time::Instant; + +".to_string()), " let sources = self_compile_sources(); + +".to_string()), " eprintln!( +".to_string()), " \"\n=== PER-MODULE RECONCILE PROFILE ({} sources) ===\", +".to_string()), " sources.len() +".to_string()), " ); + +".to_string()), " let t0 = Instant::now(); +".to_string()), " let mut modules = Vec::new(); +".to_string()), " let mut intern_table = crate::v1_std_core::empty_intern_table(); +".to_string()), " for source in &sources { +".to_string()), " let tokens = crate::v1_compiler_tokenize::tokenize( +".to_string()), " source.content.clone(), +".to_string()), " source.path.clone(), +".to_string()), " ); +".to_string()), " let si = crate::v1_std_core::build_newline_index( +".to_string()), " source.path.clone(), +".to_string()), " source.content.clone(), +".to_string()), " ); +".to_string()), " let parsed = crate::v1_compiler_parse::parse_with_table( +".to_string()), " tokens.clone(), +".to_string()), " crate::v1_rt::rc_map_insert( +".to_string()), " crate::v1_rt::rc_empty_map::>(), +".to_string()), " si.file.clone(), +".to_string()), " si.clone(), +".to_string()), " ), +".to_string()), " intern_table.clone(), +".to_string()), " ); +".to_string()), " let result = parsed.result.clone(); +".to_string()), " intern_table = parsed.intern_table.clone(); +".to_string()), " let m = result.module.clone() +".to_string()), " .unwrap_or_else(|| panic!(\"parse failed for source {}\", source.path)); +".to_string()), " modules.push(m); +".to_string()), " } +".to_string()), " let resolve_si = sources.iter().fold( +".to_string()), " crate::v1_rt::rc_empty_map::>(), +".to_string()), " |acc, s| crate::v1_rt::rc_map_insert(acc, s.path.clone(), crate::v1_std_core::build_newline_index(s.path.clone(), s.content.clone())), +".to_string()), " ); +".to_string()), " let graph = crate::v1_compiler_resolve::resolve_modules(std::rc::Rc::new(modules), resolve_si); +".to_string()), " let setup_time = t0.elapsed(); +".to_string()), " let rss_baseline = get_rss_bytes(); +".to_string()), " eprintln!( +".to_string()), " \" Setup (tok+parse+resolve): {:?} | RSS: {}\", +".to_string()), " setup_time, +".to_string()), " format_bytes(rss_baseline) +".to_string()), " ); +".to_string()), " eprintln!(\" Modules to reconcile: {}\n\", graph.modules.len()); + +".to_string()), " let mut mi_raw = HashMap::< +".to_string()), " String, +".to_string()), " std::rc::Rc, +".to_string()), " >::new(); +".to_string()), " let source_indices = std::rc::Rc::new(sources.iter().fold( +".to_string()), " HashMap::>::new(), +".to_string()), " |mut acc, source| { +".to_string()), " acc.insert( +".to_string()), " source.path.clone(), +".to_string()), " crate::v1_std_core::build_newline_index( +".to_string()), " source.path.clone(), +".to_string()), " source.content.clone(), +".to_string()), " ), +".to_string()), " ); +".to_string()), " acc +".to_string()), " }, +".to_string()), " )); +".to_string()), " let mut variant_surfaces = crate::v1_rt::rc_empty_map::< +".to_string()), " String, +".to_string()), " std::rc::Rc, +".to_string()), " >(); + +".to_string()), " for resolved in graph.modules.iter() { +".to_string()), " let name = resolved.module.name.to_string(); +".to_string()), " let item_count = +".to_string()), " crate::v1_std_core::module_items(resolved.module.clone()).len(); +".to_string()), " let rss_before = get_rss_bytes(); + +".to_string()), " eprint!(\" {:>35} ({:>3} items) ... \", name, item_count); + +".to_string()), " let module_index = std::rc::Rc::new(mi_raw.clone()); + +".to_string()), " let t_unres = Instant::now(); +".to_string()), " let _unres = crate::v1_compiler_infer::build_type_env_unresolved( +".to_string()), " resolved.clone(), +".to_string()), " module_index.clone(), +".to_string()), " source_indices.clone(), +".to_string()), " intern_table.clone(), +".to_string()), " ); +".to_string()), " let unres_elapsed = t_unres.elapsed(); +".to_string()), " let rss_after_unres = get_rss_bytes(); +".to_string()), " let unres_delta = rss_after_unres.saturating_sub(rss_before); + +".to_string()), " eprint!( +".to_string()), " \"cycles={:>8.2?}(+{}) \", +".to_string()), " unres_elapsed, +".to_string()), " format_bytes(unres_delta) +".to_string()), " ); + +".to_string()), " if unres_delta > 256 * 1024 * 1024 { +".to_string()), " eprintln!(\"\"); +".to_string()), " panic!( +".to_string()), " \"ABORT: '{}' cycle detection grew RSS by {}\", +".to_string()), " name, +".to_string()), " format_bytes(unres_delta) +".to_string()), " ); +".to_string()), " } + +".to_string()), " let t_env = Instant::now(); +".to_string()), " let env_result = crate::v1_compiler_infer::build_type_env( +".to_string()), " resolved.clone(), +".to_string()), " module_index.clone(), +".to_string()), " source_indices.clone(), +".to_string()), " intern_table.clone(), +".to_string()), " ); +".to_string()), " let env_elapsed = t_env.elapsed(); +".to_string()), " let rss_after_env = get_rss_bytes(); +".to_string()), " let env_delta = rss_after_env.saturating_sub(rss_before); +".to_string()), " let env_errs: usize = env_result +".to_string()), " .diagnostics +".to_string()), " .iter() +".to_string()), " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())) +".to_string()), " .count(); + +".to_string()), " eprint!( +".to_string()), " \"env={:>8.2?}(+{},e={}) \", +".to_string()), " env_elapsed, +".to_string()), " format_bytes(env_delta), +".to_string()), " env_errs +".to_string()), " ); + +".to_string()), " if env_delta > 512 * 1024 * 1024 { +".to_string()), " eprintln!(\"\"); +".to_string()), " panic!( +".to_string()), " \"ABORT: '{}' build_type_env grew RSS by {}\", +".to_string()), " name, +".to_string()), " format_bytes(env_delta) +".to_string()), " ); +".to_string()), " } +".to_string()), " if env_elapsed.as_secs() > 10 { +".to_string()), " eprintln!(\"\"); +".to_string()), " panic!(\"ABORT: '{}' build_type_env took {:?}\", name, env_elapsed); +".to_string()), " } + +".to_string()), " let t_full = Instant::now(); +".to_string()), " let tc_result = crate::v1_compiler_infer::typecheck_module( +".to_string()), " resolved.clone(), +".to_string()), " module_index.clone(), +".to_string()), " variant_surfaces.clone(), +".to_string()), " source_indices.clone(), +".to_string()), " intern_table.clone(), +".to_string()), " ); +".to_string()), " let full_elapsed = t_full.elapsed(); +".to_string()), " let rss_after = get_rss_bytes(); +".to_string()), " let delta = rss_after.saturating_sub(rss_before); +".to_string()), " let diag_count: usize = tc_result +".to_string()), " .diagnostics +".to_string()), " .iter() +".to_string()), " .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())) +".to_string()), " .count(); + +".to_string()), " eprintln!( +".to_string()), " \"full={:>8.2?} | RSS: {} (+{}) | errs: {}\", +".to_string()), " full_elapsed, +".to_string()), " format_bytes(rss_after), +".to_string()), " format_bytes(delta), +".to_string()), " diag_count +".to_string()), " ); + +".to_string()), " if delta > 512 * 1024 * 1024 { +".to_string()), " panic!( +".to_string()), " \"ABORT: '{}' grew RSS by {} (>512MB)\", +".to_string()), " name, +".to_string()), " format_bytes(delta) +".to_string()), " ); +".to_string()), " } +".to_string()), " if full_elapsed.as_secs() > 10 { +".to_string()), " panic!(\"ABORT: '{}' took {:?} (>10s)\", name, full_elapsed); +".to_string()), " } + +".to_string()), " let typed = tc_result.typed.clone(); +".to_string()), " let typed_path = crate::v1_std_core::authored_name_at( +".to_string()), " source_indices.clone(), +".to_string()), " typed.module.clone(), +".to_string()), " ); +".to_string()), " variant_surfaces = crate::v1_rt::rc_map_insert( +".to_string()), " variant_surfaces.clone(), +".to_string()), " typed_path, +".to_string()), " crate::v1_compiler_infer::build_variant_export_surface( +".to_string()), " typed.clone(), +".to_string()), " variant_surfaces.clone(), +".to_string()), " source_indices.clone(), +".to_string()), " ), +".to_string()), " ); +".to_string()), " mi_raw.insert(name, typed); +".to_string()), " } + +".to_string()), " let rss_final = get_rss_bytes(); +".to_string()), " eprintln!( +".to_string()), " \"\n RSS final: {} (from baseline: +{})\", +".to_string()), " format_bytes(rss_final), +".to_string()), " format_bytes(rss_final.saturating_sub(rss_baseline)) +".to_string()), " ); +".to_string()), " eprintln!(\"=== DONE ===\n\"); +".to_string()), " }) +".to_string()), " .expect(\"failed to spawn thread\") +".to_string()), " .join(); +".to_string()), " result.expect(\"profile_reconcile_per_module panicked\"); +".to_string()), " } + +".to_string()) } pub fn ct_module_footer() -> String { diff --git a/src/v1/stage0/src/v1_compiler_dag_collect.rs b/src/v1/stage0/src/v1_compiler_dag_collect.rs index ddc2323061c..27abc1ea3fd 100644 --- a/src/v1/stage0/src/v1_compiler_dag_collect.rs +++ b/src/v1/stage0/src/v1_compiler_dag_collect.rs @@ -2,11 +2,10 @@ // Source module: v1.compiler.dag_collect pub use crate::v1_compiler_dag_collect_support::{ - dag_collect_fp_memo_reset, dag_collect_pack_slots, dag_node_collection_anchor, - dag_node_is_resolved_identity_shell, dag_node_surface_fingerprint, + dag_collect_fp_memo_reset, dag_node_key_collision_error, dag_node_surface_fingerprint, dag_node_surface_fingerprint_memo, }; -pub use crate::v1_compiler_dag_collect_support::{DagCollectAcc, DagCollectSlot}; +pub use crate::v1_compiler_dag_collect_support::{DagCollectAcc, DagCollectPending}; pub use crate::v1_compiler_infer_items::{ResolvedGraph, TypedModule}; use crate::v1_rt; use crate::v1_rt::Witness; @@ -16,7 +15,7 @@ use crate::v1_std_core::Connective::NoConnective; use crate::v1_std_core::ExprData::*; use crate::v1_std_core::InferredNode::Resolved; use crate::v1_std_core::MatchPattern::*; -pub use crate::v1_std_core::{Connective, ErrorNode, ExprData, InferredNode, MatchPattern, Node}; +pub use crate::v1_std_core::{Connective, ExprData, InferredNode, MatchPattern, Node}; use crate::NonEmptyBTreeSet; use crate::NonEmptyVec; use std::cell::RefCell; @@ -32,32 +31,6 @@ fn dag_collect_key_memo_reset() { DAG_NODE_KEY_MEMO.with(|memo| memo.borrow_mut().clear()); } -fn dag_node_key_uncached(anchor: Rc) -> String { - if ((anchor.span.clone().start.clone() == 0) && (anchor.span.clone().end.clone() == 0)) { - v1_rt::concat( - ":0..0:".to_string(), - dag_node_surface_fingerprint_memo(anchor.clone()), - ) - } else { - v1_rt::concat( - v1_rt::concat( - v1_rt::concat( - v1_rt::concat( - v1_rt::concat(anchor.span.clone().file.clone(), ":".to_string()), - (anchor.span.clone().start.clone()).to_string(), - ), - "..".to_string(), - ), - (anchor.span.clone().end.clone()).to_string(), - ), - match anchor.ident.clone() { - Some(id) => v1_rt::concat(":".to_string(), (id.clone()).to_string()), - None => "".to_string(), - }, - ) - } -} - pub fn is_import_slot_node(n: Rc) -> bool { (import_is_all(n.clone()) || (((((n.params.clone().len() as i64) == 0) && (n.ident_span.clone() != None)) @@ -84,6 +57,65 @@ pub fn is_module_shell_node(n: Rc) -> bool { }) } +pub fn dag_node_is_resolved_identity_shell(node: Rc) -> bool { + match (*node.expr_data.clone()).clone() { + ExprData::NoExprData => match node.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: _, .. }) => { + ((((node.body.clone() == None) && (node.transport.clone() == None)) + && ((node.children.clone().len() as i64) == 0)) + && ((node.params.clone().len() as i64) == 0)) + } + _ => false, + }, + _ => false, + } +} + +pub fn dag_node_collection_anchor(mut node: Rc) -> Rc { + loop { + if dag_node_is_resolved_identity_shell(node.clone()) { + match node.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: target, .. }) => { + let __tco_0 = target.clone(); + node = __tco_0; + continue; + } + _ => { + break node.clone(); + } + } + } else { + break node.clone(); + } + } +} + +fn dag_node_key_uncached(anchor: Rc) -> String { + if ((anchor.span.clone().start.clone() == 0) && (anchor.span.clone().end.clone() == 0)) { + v1_rt::concat( + ":0..0:".to_string(), + dag_node_surface_fingerprint_memo(anchor.clone()), + ) + } else { + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat(anchor.span.clone().file.clone(), ":".to_string()), + (anchor.span.clone().start.clone()).to_string(), + ), + "..".to_string(), + ), + (anchor.span.clone().end.clone()).to_string(), + ), + match anchor.ident.clone() { + Some(id) => v1_rt::concat(":".to_string(), (id.clone()).to_string()), + None => "".to_string(), + }, + ) + } +} + pub fn dag_node_key(node: Rc) -> String { let anchor = dag_node_collection_anchor(node); let ptr = Rc::as_ptr(&anchor) as usize; @@ -103,147 +135,147 @@ pub fn dag_node_fingerprint(node: Rc) -> String { pub fn dag_collect_nodes_list( nodes: Rc>>, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { - nodes.iter().cloned().fold( - slots, - |s: Rc>>, n: Rc| { - dag_collect_insert_slots(n, s, collision_errors.clone()) - }, - ) + acc: Rc, +) -> Rc { + nodes + .iter() + .cloned() + .fold(acc.clone(), |a: Rc, n: Rc| { + dag_collect_insert(n.clone(), a) + }) } pub fn dag_collect_optional_node( value: Option>, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { + acc: Rc, +) -> Rc { match value { - Some(inner) => dag_collect_insert_slots(inner, slots, collision_errors), - None => slots, + Some(inner) => dag_collect_insert(inner.clone(), acc), + None => acc, } } pub fn dag_collect_inferred( value: Option>, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { + acc: Rc, +) -> Rc { match value.as_deref().cloned() { - Some(InferredNode::Resolved { node: n, .. }) => { - dag_collect_insert_slots(n, slots, collision_errors) - } - _ => slots, + Some(InferredNode::Resolved { node: n, .. }) => dag_collect_insert(n.clone(), acc), + _ => acc, } } pub fn dag_collect_match_pattern( pattern: Rc, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { + acc: Rc, +) -> Rc { match (*pattern).clone() { - MatchPattern::Bind { name: _, .. } => slots, - MatchPattern::LitPattern { value: _, .. } => slots, - MatchPattern::VariantPattern { field_bindings, .. } => { - field_bindings.clone().iter().cloned().fold( - slots, - |s: Rc>>, fb: Rc| { - dag_collect_insert_slots(fb, s, collision_errors.clone()) - }, - ) - } - MatchPattern::Wildcard => slots, + MatchPattern::Bind { name: _, .. } => acc, + MatchPattern::LitPattern { value: _, .. } => acc, + MatchPattern::VariantPattern { field_bindings, .. } => field_bindings + .clone() + .iter() + .cloned() + .fold(acc, |a: Rc, fb: Rc| { + dag_collect_insert(fb.clone(), a) + }), + MatchPattern::Wildcard => acc, } } -pub fn dag_collect_node_tree( - node: Rc, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { - let slots = dag_collect_nodes_list(node.children.clone(), slots, collision_errors.clone()); - let slots = if is_module_shell_node(node.clone()) { - slots - } else { - dag_collect_nodes_list(node.params.clone(), slots, collision_errors.clone()) - }; - let slots = dag_collect_nodes_list(node.uses.clone(), slots, collision_errors.clone()); - let slots = dag_collect_optional_node(node.body.clone(), slots, collision_errors.clone()); - let slots = dag_collect_optional_node(node.transport.clone(), slots, collision_errors.clone()); - let slots = dag_collect_nodes_list(node.properties.clone(), slots, collision_errors.clone()); - let slots = dag_collect_optional_node( - node.type_annotation.clone(), - slots, - collision_errors.clone(), - ); - let slots = dag_collect_inferred(node.inferred.clone(), slots, collision_errors.clone()); - match node.match_pattern.clone() { - Some(p) => dag_collect_match_pattern(p, slots, collision_errors), - None => slots, +pub fn dag_collect_node_tree(node: Rc, acc: Rc) -> Rc { + { + let acc = dag_collect_nodes_list(node.children.clone(), acc.clone()); + let acc = if is_module_shell_node(node.clone()) { + acc.clone() + } else { + dag_collect_nodes_list(node.params.clone(), acc.clone()) + }; + let acc = dag_collect_nodes_list(node.uses.clone(), acc.clone()); + let acc = dag_collect_optional_node(node.body.clone(), acc.clone()); + let acc = dag_collect_optional_node(node.transport.clone(), acc.clone()); + let acc = dag_collect_nodes_list(node.properties.clone(), acc.clone()); + let acc = dag_collect_optional_node(node.type_annotation.clone(), acc.clone()); + let acc = dag_collect_inferred(node.inferred.clone(), acc.clone()); + match node.match_pattern.clone() { + Some(p) => dag_collect_match_pattern(p.clone(), acc.clone()), + None => acc.clone(), + } } } -pub fn dag_collect_insert_slots( - node: Rc, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { - let anchor = dag_node_collection_anchor(node); - let key = dag_node_key(anchor.clone()); - match v1_rt::map_get(&slots, key.clone()) { - Some(_) => slots, - None => { - let fp = if ((anchor.span.clone().start.clone() == 0) - && (anchor.span.clone().end.clone() == 0)) - { - v1_rt::substring(&key, 6, v1_rt::string_length(&key)) - } else { - dag_node_fingerprint(anchor.clone()) - }; - let seq = slots.len() as i64; - dag_collect_node_tree( - anchor.clone(), - v1_rt::rc_map_insert( - slots, - key.clone(), - Rc::new(DagCollectSlot { - key, - fp, - node: anchor, - seq, - }), - ), - collision_errors, - ) +pub fn dag_collect_insert(node: Rc, acc: Rc) -> Rc { + { + let anchor = dag_node_collection_anchor(node); + let key = dag_node_key(anchor.clone()); + match v1_rt::map_get(&acc.seen.clone(), key.clone()) { + Some(_) => acc.clone(), + None => { + let fp = if ((anchor.span.clone().start.clone() == 0) + && (anchor.span.clone().end.clone() == 0)) + { + v1_rt::substring(&key, 6, v1_rt::string_length(&key)) + } else { + dag_node_fingerprint(anchor.clone()) + }; + Rc::new(vec![Rc::new(DagCollectPending { + anchor: anchor.clone(), + key: key.clone(), + fp: fp, + })]) + .iter() + .cloned() + .fold( + acc.clone(), + |inner: Rc, pending: Rc| { + dag_collect_node_tree( + pending.anchor.clone(), + Rc::new(DagCollectAcc { + seen: v1_rt::rc_map_insert( + inner.seen.clone(), + pending.key.clone(), + pending.fp.clone(), + ), + order: v1_rt::rc_list_push( + inner.order.clone(), + pending.anchor.clone(), + ), + collision_errors: inner.collision_errors.clone(), + }), + ) + }, + ) + } } } } pub fn dag_collect_from_module( module: Rc, - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc>> { - let slots = dag_collect_insert_slots(module.module.clone(), slots, collision_errors.clone()); - module.items.clone().iter().cloned().fold( - slots, - |s: Rc>>, item: Rc| { - dag_collect_insert_slots(item, s, collision_errors.clone()) - }, - ) + acc: Rc, +) -> Rc { + { + let acc = dag_collect_insert(module.module.clone(), acc); + module + .items + .clone() + .iter() + .cloned() + .fold(acc, |a: Rc, item: Rc| { + dag_collect_insert(item.clone(), a) + }) + } } pub fn collect_dag_nodes(typed: Rc) -> Rc { dag_collect_fp_memo_reset(); dag_collect_key_memo_reset(); - let collision_errors = Rc::new(vec![]); - let slots = typed.modules.clone().iter().cloned().fold( - v1_rt::rc_empty_map::>(), - |s: Rc>>, m: Rc| { - dag_collect_from_module(m, s, collision_errors.clone()) - }, - ); - dag_collect_pack_slots(slots, collision_errors) + typed.modules.clone().iter().cloned().fold( + Rc::new(DagCollectAcc { + seen: v1_rt::rc_empty_map::(), + order: Rc::new(vec![]), + collision_errors: Rc::new(vec![]), + }), + |acc: Rc, m: Rc| dag_collect_from_module(m.clone(), acc), + ) } diff --git a/src/v1/stage0/src/v1_compiler_dag_collect_support.rs b/src/v1/stage0/src/v1_compiler_dag_collect_support.rs index 31ff03253f7..166562243bf 100644 --- a/src/v1/stage0/src/v1_compiler_dag_collect_support.rs +++ b/src/v1/stage0/src/v1_compiler_dag_collect_support.rs @@ -29,84 +29,6 @@ pub fn dag_collect_fp_memo_reset() { DAG_NODE_SURFACE_FP_MEMO.with(|memo| memo.borrow_mut().clear()); } -pub fn dag_node_is_resolved_identity_shell(node: Rc) -> bool { - match (*node.expr_data.clone()).clone() { - ExprData::NoExprData => match node.inferred.clone().as_deref().cloned() { - Some(InferredNode::Resolved { node: _, .. }) => { - ((((node.body.clone() == None) && (node.transport.clone() == None)) - && ((node.children.clone().len() as i64) == 0)) - && ((node.params.clone().len() as i64) == 0)) - } - _ => false, - }, - _ => false, - } -} - -pub fn dag_node_collection_anchor(mut node: Rc) -> Rc { - loop { - if dag_node_is_resolved_identity_shell(node.clone()) { - match node.inferred.clone().as_deref().cloned() { - Some(InferredNode::Resolved { node: target, .. }) => { - let __tco_0 = target.clone(); - node = __tco_0; - continue; - } - _ => { - break node.clone(); - } - } - } else { - break node.clone(); - } - } -} - -fn dag_node_surface_fingerprint_compute(node: Rc) -> String { - stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { - let child_hashes = Rc::new({ - let mut __result = Vec::new(); - for c in node.children.clone().iter().cloned() { - __result.push(dag_node_surface_fingerprint_memo(c.clone())); - } - __result - }); - let param_hashes = Rc::new({ - let mut __result = Vec::new(); - for p in node.params.clone().iter().cloned() { - __result.push(dag_node_surface_fingerprint_memo(p.clone())); - } - __result - }); - let with_children = v1_rt::hash_combine( - dag_node_surface_leaf_mix(node.clone()), - child_subtree_hash(node.connective.clone(), child_hashes), - ); - v1_rt::hash_combine(with_children, dag_node_seq_hash(param_hashes)) - }) -} - -pub fn dag_node_surface_fingerprint_memo(node: Rc) -> String { - let anchor = dag_node_collection_anchor(node); - let ptr = Rc::as_ptr(&anchor) as usize; - DAG_NODE_SURFACE_FP_MEMO.with(|memo| { - if let Some(fp) = memo.borrow().get(&ptr) { - return fp.clone(); - } - let fp = dag_node_surface_fingerprint_compute(anchor); - memo.borrow_mut().insert(ptr, fp.clone()); - fp - }) -} - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct DagCollectSlot { - pub key: String, - pub fp: String, - pub node: Rc, - pub seq: i64, -} - #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DagCollectAcc { pub seen: Rc>, @@ -114,28 +36,11 @@ pub struct DagCollectAcc { pub collision_errors: Rc>>, } -pub fn dag_collect_slot_seq(slots: Rc>>) -> i64 { - slots.len() as i64 -} - -pub fn dag_collect_pack_slots( - slots: Rc>>, - collision_errors: Rc>>, -) -> Rc { - let mut ordered_slots: Vec> = slots.values().cloned().collect(); - ordered_slots.sort_by(|a, b| a.seq.cmp(&b.seq)); - let order: Vec> = ordered_slots.iter().map(|s| s.node.clone()).collect(); - let seen = ordered_slots.iter().cloned().fold( - v1_rt::rc_empty_map::(), - |acc: Rc>, s: Rc| { - v1_rt::rc_map_insert(acc, s.key.clone(), s.fp.clone()) - }, - ); - Rc::new(DagCollectAcc { - seen, - order: Rc::new(order), - collision_errors, - }) +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DagCollectPending { + pub anchor: Rc, + pub key: String, + pub fp: String, } pub fn json_quote(s: String) -> String { @@ -252,6 +157,42 @@ pub fn dag_node_surface_leaf_mix(node: Rc) -> String { )) } +fn dag_node_surface_fingerprint_compute(node: Rc) -> String { + stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { + let child_hashes = Rc::new({ + let mut __result = Vec::new(); + for c in node.children.clone().iter().cloned() { + __result.push(dag_node_surface_fingerprint_memo(c.clone())); + } + __result + }); + let param_hashes = Rc::new({ + let mut __result = Vec::new(); + for p in node.params.clone().iter().cloned() { + __result.push(dag_node_surface_fingerprint_memo(p.clone())); + } + __result + }); + let with_children = v1_rt::hash_combine( + dag_node_surface_leaf_mix(node.clone()), + child_subtree_hash(node.connective.clone(), child_hashes), + ); + v1_rt::hash_combine(with_children, dag_node_seq_hash(param_hashes)) + }) +} + +pub fn dag_node_surface_fingerprint_memo(node: Rc) -> String { + let ptr = Rc::as_ptr(&node) as usize; + DAG_NODE_SURFACE_FP_MEMO.with(|memo| { + if let Some(fp) = memo.borrow().get(&ptr) { + return fp.clone(); + } + let fp = dag_node_surface_fingerprint_compute(node); + memo.borrow_mut().insert(ptr, fp.clone()); + fp + }) +} + pub fn dag_node_surface_fingerprint_rec(node: Rc) -> String { dag_node_surface_fingerprint_memo(node) } diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 0525069bf0f..eb3e93db183 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -202,6 +202,18 @@ pub struct VariantFoldState { pub collision_errors: Rc>>, } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct VariantExportSurface { + pub arm_owners: Rc>>, + pub enum_items: Rc>>, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TypeNameExportFacts { + pub exporter_count: i64, + pub canonical_binding: Option>, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct InferScope { pub type_env: Rc, @@ -13571,97 +13583,112 @@ pub fn build_imported_variants( ) } -// HAND-PATCH (constructor-owner ruling ยง1c; seed two-step): re-export chain -// followers โ€” a module re-exports its specific-name imports, so the binder -// walks the acyclic import chain to the defining module's coproduct. Replaced -// by regen's true emission in the convergence commit. -pub fn owner_of_exported_arm( - module_path: String, - arm_name: String, - parent_index: Rc>>, +pub fn empty_variant_export_surface() -> Rc { + Rc::new(VariantExportSurface { + arm_owners: v1_rt::rc_empty_map::>(), + enum_items: v1_rt::rc_empty_map::>(), + }) +} + +pub fn collect_own_variant_export_surface( + items: Rc>>, source_indices: Rc>>, -) -> Option> { - match v1_rt::map_get(&parent_index, module_path) { - None => None, - Some(tm) => { - let own = tm.items.iter().cloned().find(|item| { - item.connective.clone() == Connective::Disj - && item - .children - .iter() - .cloned() - .any(|c| authored_name_at(source_indices.clone(), c) == arm_name) - }); - match own { - Some(item) => Some(item), - None => { - let reexport = module_imports(tm.module.clone()) - .iter() - .cloned() - .find(|imp| { - !import_is_all(imp.clone()) - && import_specific_names_at(imp.clone(), source_indices.clone()) - .iter() - .any(|n| *n == arm_name) - }); - match reexport { - Some(imp) => owner_of_exported_arm( - authored_name_at(source_indices.clone(), imp), - arm_name, - parent_index, - source_indices, - ), - None => None, - } - } +) -> Rc { + items + .iter() + .cloned() + .fold(empty_variant_export_surface(), |acc, item| { + if item.connective.clone() == Connective::Disj { + let enum_items = v1_rt::rc_map_insert( + acc.enum_items.clone(), + authored_name_at(source_indices.clone(), item.clone()), + item.clone(), + ); + let arm_owners = item.children.clone().iter().cloned().fold( + acc.arm_owners.clone(), + |arms: Rc>>, child: Rc| { + v1_rt::rc_map_insert( + arms, + authored_name_at(source_indices.clone(), child), + item.clone(), + ) + }, + ); + Rc::new(VariantExportSurface { + arm_owners, + enum_items, + }) + } else { + acc } - } - } + }) } -pub fn exported_coproduct_item( - module_path: String, - enum_name: String, - parent_index: Rc>>, +fn reexport_variant_surface_fragment( + imp: Rc, + surfaces: Rc>>, source_indices: Rc>>, -) -> Option> { - match v1_rt::map_get(&parent_index, module_path) { - None => None, - Some(tm) => { - let own = tm.items.iter().cloned().find(|item| { - item.connective.clone() == Connective::Disj - && authored_name_at(source_indices.clone(), item.clone()) == enum_name - }); - match own { - Some(item) => Some(item), - None => { - let reexport = module_imports(tm.module.clone()) - .iter() - .cloned() - .find(|imp| { - !import_is_all(imp.clone()) - && import_specific_names_at(imp.clone(), source_indices.clone()) - .iter() - .any(|n| *n == enum_name) - }); - match reexport { - Some(imp) => exported_coproduct_item( - authored_name_at(source_indices.clone(), imp), - enum_name, - parent_index, - source_indices, +) -> Rc { + let proxy_path = import_module_path_at(imp.clone(), source_indices.clone()); + match v1_rt::map_get(&surfaces, proxy_path) { + None => empty_variant_export_surface(), + Some(proxy) => import_specific_names_at(imp, source_indices.clone()) + .iter() + .cloned() + .fold(empty_variant_export_surface(), |acc, name| { + let with_arm = match v1_rt::map_get(&proxy.arm_owners, name.clone()) { + Some(owner) => Rc::new(VariantExportSurface { + arm_owners: v1_rt::rc_map_insert( + acc.arm_owners.clone(), + name.clone(), + owner, ), - None => None, - } + enum_items: acc.enum_items.clone(), + }), + None => acc.clone(), + }; + match v1_rt::map_get(&proxy.enum_items, name.clone()) { + Some(item) => Rc::new(VariantExportSurface { + arm_owners: with_arm.arm_owners.clone(), + enum_items: v1_rt::rc_map_insert(with_arm.enum_items.clone(), name, item), + }), + None => with_arm, } - } - } + }), } } +pub fn build_variant_export_surface( + tm: Rc, + surfaces: Rc>>, + source_indices: Rc>>, +) -> Rc { + let own = collect_own_variant_export_surface(tm.items.clone(), source_indices.clone()); + let reexports = module_imports(tm.module.clone()) + .iter() + .cloned() + .filter(|imp| !import_is_all(imp.clone())) + .fold(empty_variant_export_surface(), |acc, imp| { + let frag = + reexport_variant_surface_fragment(imp, surfaces.clone(), source_indices.clone()); + Rc::new(VariantExportSurface { + arm_owners: v1_rt::rc_map_merge(acc.arm_owners.clone(), frag.arm_owners.clone()), + enum_items: v1_rt::rc_map_merge(acc.enum_items.clone(), frag.enum_items.clone()), + }) + }); + Rc::new(VariantExportSurface { + arm_owners: v1_rt::rc_map_merge(reexports.arm_owners.clone(), own.arm_owners.clone()), + enum_items: v1_rt::rc_map_merge(reexports.enum_items.clone(), own.enum_items.clone()), + }) +} + +// HAND-PATCH (P3+P5): per-module variant export surface replaces whole-corpus +// Disj-scan + re-export chain walk per imported name. Dissolves at v2 regen. + pub fn build_module_context( contributions: Rc>>, parent_index: Rc>>, + variant_surfaces: Rc>>, resolved_imports: Rc>>, env: Rc, module_name: String, @@ -13779,26 +13806,17 @@ pub fn build_module_context( } else { acc }; - // Specific names resolve through the re-export chain to the - // defining module (owner_of_exported_arm / exported_coproduct_item). + // Specific names resolve through the precomputed per-module export surface. imp.specific_names.iter().cloned().fold( with_glob, |nacc: Rc, name: String| { - let after_enum = match exported_coproduct_item( - imp.module_path.clone(), - name.clone(), - parent_index.clone(), - env.source_indices.clone(), - ) { + let surface = v1_rt::map_get(&variant_surfaces, imp.module_path.clone()) + .unwrap_or_else(empty_variant_export_surface); + let after_enum = match v1_rt::map_get(&surface.enum_items, name.clone()) { Some(item) => bind_coproduct_item_arms(nacc, item), None => nacc, }; - match owner_of_exported_arm( - imp.module_path.clone(), - name.clone(), - parent_index.clone(), - env.source_indices.clone(), - ) { + match v1_rt::map_get(&surface.arm_owners, name.clone()) { Some(owner) => insert_variant_owner_checked(after_enum, name, owner), None => after_enum, } @@ -13879,6 +13897,7 @@ pub struct TypecheckModuleResult { pub fn typecheck_module( resolved: Rc, parent_index: Rc>>, + variant_surfaces: Rc>>, source_indices: Rc>>, intern_table: Rc, ) -> Rc { @@ -13934,6 +13953,7 @@ pub fn typecheck_module( let ctx = build_module_context( contributions, parent_index.clone(), + variant_surfaces.clone(), resolved.resolved_imports.clone(), env.clone(), resolved_module_name.clone(), @@ -14064,6 +14084,21 @@ pub fn typecheck_module( } } +pub fn typecheck_module_isolated( + resolved: Rc, + parent_index: Rc>>, + source_indices: Rc>>, + intern_table: Rc, +) -> Rc { + typecheck_module( + resolved, + parent_index, + Rc::new(HashMap::new()), + source_indices, + intern_table, + ) +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct EnvResolveResult { pub env: Rc, @@ -14656,6 +14691,7 @@ pub fn typecheck( graph.modules.clone(), Rc::new(vec![]), v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::>(), v1_rt::rc_empty_map::>(), Rc::new(vec![]), source_indices, @@ -14668,6 +14704,7 @@ pub fn typecheck_modules( mut remaining: Rc>>, mut modules: Rc>>, mut module_index: Rc>>, + mut variant_surfaces: Rc>>, mut item_registry: Rc>>, mut diag_chunks: Rc>>>>, mut source_indices: Rc>>, @@ -14699,11 +14736,22 @@ pub fn typecheck_modules( let tc_result = typecheck_module( resolved.clone(), module_index.clone(), + variant_surfaces.clone(), source_indices.clone(), intern_table.clone(), ); let typed = tc_result.typed.clone(); let tc_diags = tc_result.diagnostics.clone(); + let typed_path = authored_name_at(source_indices.clone(), typed.module.clone()); + variant_surfaces = v1_rt::rc_map_insert( + variant_surfaces.clone(), + typed_path.clone(), + build_variant_export_surface( + typed.clone(), + variant_surfaces.clone(), + source_indices.clone(), + ), + ); { let __tco_0 = Rc::new( remaining @@ -14713,11 +14761,7 @@ pub fn typecheck_modules( .collect::>(), ); let __tco_1 = v1_rt::rc_list_push(modules, typed.clone()); - let __tco_2 = v1_rt::rc_map_insert( - module_index, - authored_name_at(source_indices.clone(), typed.module.clone()), - typed.clone(), - ); + let __tco_2 = v1_rt::rc_map_insert(module_index, typed_path, typed.clone()); let __tco_3 = v1_rt::rc_map_merge(item_registry, typed.item_registry.clone()); let __tco_4 = v1_rt::rc_list_push( v1_rt::rc_list_push(diag_chunks, parent_result.diagnostics.clone()), @@ -14742,38 +14786,58 @@ pub fn import_module_path_at( authored_name_at(source_indices, imp) } +pub fn build_type_name_export_index( + modules: Rc>>, +) -> Rc>> { + modules.iter().cloned().fold( + v1_rt::rc_empty_map::>(), + |acc: Rc>>, m: Rc| { + let mut seen_names = std::collections::HashSet::::new(); + m.type_env + .bindings + .values() + .cloned() + .fold(acc, |acc2, binding| { + let name = binding.name.clone(); + if !seen_names.insert(name.clone()) { + return acc2; + } + let canonical = m + .type_env + .bindings + .values() + .filter(|b| b.name == name) + .next() + .cloned() + .unwrap_or(binding); + match v1_rt::map_get(&acc2, name.clone()) { + None => v1_rt::rc_map_insert( + acc2, + name, + Rc::new(TypeNameExportFacts { + exporter_count: 1, + canonical_binding: Some(canonical), + }), + ), + Some(facts) => v1_rt::rc_map_insert( + acc2, + name, + Rc::new(TypeNameExportFacts { + exporter_count: facts.exporter_count + 1, + canonical_binding: Some(canonical), + }), + ), + } + }) + }, + ) +} + pub fn rewire_type_env_import_str_binding_identity( modules: Rc>>, source_indices: Rc>>, ) -> Rc>> { record_rewire_type_env_import_str_binding_call(); - fn local_authority_binding(owner: Rc, name: String) -> Option> { - owner - .type_env - .bindings - .values() - .find(|b| b.name.clone() == name) - .cloned() - } - - fn global_type_exporter_count(modules: Rc>>, name: &str) -> usize { - modules - .iter() - .filter(|m| module_exports_type_name((**m).clone(), name)) - .count() - } - - fn local_authority_for_name( - modules: Rc>>, - name: String, - ) -> Option> { - // ๐ŸŸก dissolve-on: local_authority_for_name_dissolution_trigger (04_infer.dag) - modules - .iter() - .rev() - .find(|m| m.type_env.bindings.values().any(|b| b.name.clone() == name)) - .cloned() - } fn module_exports_type_name(m: Rc, name: &str) -> bool { m.type_env @@ -14799,6 +14863,7 @@ pub fn rewire_type_env_import_str_binding_identity( .count() } + let type_name_index = build_type_name_export_index(modules.clone()); let index = modules.clone().iter().cloned().fold( v1_rt::rc_empty_map::>(), |acc: Rc>>, m: Rc| { @@ -14829,18 +14894,21 @@ pub fn rewire_type_env_import_str_binding_identity( .collect(); inherited_names.sort(); for name in inherited_names { + let exporter_count = v1_rt::map_get(&type_name_index, name.clone()) + .map(|facts| facts.exporter_count) + .unwrap_or(0); if direct_import_exporter_count( m.clone(), name.as_str(), &index, source_indices.clone(), ) > 1 - || global_type_exporter_count(modules.clone(), name.as_str()) > 1 + || exporter_count > 1 { continue; } - if let Some(owner) = local_authority_for_name(modules.clone(), name.clone()) { - if let Some(canonical) = local_authority_binding(owner, name.clone()) { + if let Some(facts) = v1_rt::map_get(&type_name_index, name.clone()) { + if let Some(canonical) = facts.canonical_binding.clone() { if match v1_rt::map_get(&ancestry_str_bindings, name.clone()) { Some(existing) => { Rc::ptr_eq(&existing.resolved, &canonical.resolved) diff --git a/src/v1/stage0/src/v1_compiler_infer_method.rs b/src/v1/stage0/src/v1_compiler_infer_method.rs index 96d9884ceb1..7aa50f4c8b7 100644 --- a/src/v1/stage0/src/v1_compiler_infer_method.rs +++ b/src/v1/stage0/src/v1_compiler_infer_method.rs @@ -170,13 +170,13 @@ pub fn builtin_function_registry() -> Rc>> { ); let m = v1_rt::rc_map_insert( m.clone(), - "reverse".to_string(), - list_of_type_variable("collection_element".to_string()), + "get".to_string(), + with_optional_cardinality(type_variable_node("collection_element".to_string())), ); let m = v1_rt::rc_map_insert( m.clone(), - "get".to_string(), - with_optional_cardinality(type_variable_node("collection_element".to_string())), + "reverse".to_string(), + list_of_type_variable("collection_element".to_string()), ); let m = v1_rt::rc_map_insert( m.clone(), diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index ee62412deb9..bcbda6e1cdb 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -153,6 +153,8 @@ mod v1_compiler_lib_test; #[cfg(test)] mod value_carrier_swap_test; #[cfg(test)] +mod variant_export_surface_witness_test; +#[cfg(test)] mod variant_owner_disambiguation_test; #[cfg(test)] mod wet_hermetic_equivalence_test; diff --git a/src/v1/tests/src/variant_export_surface_witness_test.rs b/src/v1/tests/src/variant_export_surface_witness_test.rs new file mode 100644 index 00000000000..9ea501c7bb3 --- /dev/null +++ b/src/v1/tests/src/variant_export_surface_witness_test.rs @@ -0,0 +1,194 @@ +//! P3+P5 purity oracle: incremental `variant_surfaces` / `type_name_index` must +//! preserve re-export variant resolution and full-pipeline diagnostics. +//! +//! RED control: `typecheck_module_isolated` (empty surfaces) must fail on a +//! re-export chain where the consumer imports a coproduct arm through a proxy. + +use std::collections::HashMap; +use std::rc::Rc; + +use v1_compiler::v1_compiler_compile::{front_end_sources, normalize_graph, SourceFile}; +use v1_compiler::v1_compiler_infer::{ + build_variant_export_surface, typecheck_module, typecheck_module_isolated, + TypecheckModuleResult, VariantExportSurface, +}; +use v1_compiler::v1_compiler_infer_items::TypedModule; +use v1_compiler::v1_compiler_resolve::ResolvedModule; +use v1_compiler::v1_rt; +use v1_compiler::v1_std_core::{ + authored_name_at, diagnostic_to_message, is_error_diagnostic, InternTable, NewlineIndex, +}; + +use crate::helpers::{compile_multi, diagnostic_messages, resolve_imports_transitively}; + +const PROVIDER: &str = "module test.provider\ntype E = A | B\n"; + +const REEXPORT: &str = "module test.reexport\nimport test.provider { B }\n"; + +const CONSUMER: &str = "module test.consumer\nimport test.reexport { B }\nfn f() -> E { B }\n"; + +type ResolvedGraphFixture = ( + Rc>>, + Rc>>, + Rc, +); + +fn fixture_sources() -> Vec> { + resolve_imports_transitively("consumer.dag", CONSUMER) + .into_iter() + .chain(resolve_imports_transitively("provider.dag", PROVIDER)) + .chain(resolve_imports_transitively("reexport.dag", REEXPORT)) + .fold(HashMap::new(), |mut acc, src| { + acc.entry(src.path.clone()).or_insert(src); + acc + }) + .into_values() + .collect() +} + +fn resolved_module_graph(sources: Rc>>) -> ResolvedGraphFixture { + let frontend = front_end_sources(sources); + let graph = frontend.graph.clone().expect("resolved module graph"); + let source_indices = frontend.newline_indices.iter().cloned().fold( + v1_rt::rc_empty_map::>(), + |acc, si| v1_rt::rc_map_insert(acc, si.file.clone(), si), + ); + let norm = normalize_graph(graph, source_indices.clone()); + ( + norm.graph.modules.clone(), + source_indices, + frontend.intern_table.clone(), + ) +} + +fn hard_diagnostic_messages(result: &TypecheckModuleResult) -> Vec { + result + .diagnostics + .iter() + .filter(|d| is_error_diagnostic(d.diagnostic.clone())) + .map(|d| diagnostic_to_message(d.diagnostic.clone())) + .collect() +} + +fn typecheck_resolved_incremental( + modules: &[Rc], + source_indices: Rc>>, + intern_table: Rc, +) -> Vec> { + let mut module_index: Rc>> = v1_rt::rc_empty_map(); + let mut variant_surfaces: Rc>> = v1_rt::rc_empty_map(); + let mut results = Vec::new(); + for resolved in modules { + let tc = typecheck_module( + resolved.clone(), + module_index.clone(), + variant_surfaces.clone(), + source_indices.clone(), + intern_table.clone(), + ); + let typed = tc.typed.clone(); + let path = authored_name_at(source_indices.clone(), typed.module.clone()); + variant_surfaces = v1_rt::rc_map_insert( + variant_surfaces.clone(), + path.clone(), + build_variant_export_surface( + typed.clone(), + variant_surfaces.clone(), + source_indices.clone(), + ), + ); + module_index = v1_rt::rc_map_insert(module_index, path, typed); + results.push(tc); + } + results +} + +fn consumer_resolved<'a>( + modules: &'a [Rc], + source_indices: &Rc>>, +) -> &'a Rc { + modules + .iter() + .find(|m| authored_name_at(source_indices.clone(), m.module.clone()) == "test.consumer") + .expect("consumer module in resolved graph") +} + +#[test] +fn variant_reexport_chain_full_pipeline_is_clean() { + let result = compile_multi(&[ + ("provider.dag", PROVIDER), + ("reexport.dag", REEXPORT), + ("consumer.dag", CONSUMER), + ]); + let msgs: Vec<_> = diagnostic_messages(&result) + .into_iter() + .filter(|m| !m.starts_with("complexity: ")) + .collect(); + assert!( + msgs.is_empty(), + "re-export variant chain must typecheck clean via production reconcile, got:\n{msgs:?}" + ); +} + +#[test] +fn variant_reexport_incremental_surfaces_match_full_pipeline_fingerprint() { + let sources = Rc::new(fixture_sources()); + let pipeline = compile_multi(&[ + ("provider.dag", PROVIDER), + ("reexport.dag", REEXPORT), + ("consumer.dag", CONSUMER), + ]); + let pipeline_msgs: Vec<_> = diagnostic_messages(&pipeline) + .into_iter() + .filter(|m| !m.starts_with("complexity: ")) + .collect(); + + let (modules, source_indices, intern_table) = resolved_module_graph(sources); + let incremental = typecheck_resolved_incremental(&modules, source_indices, intern_table); + let consumer_tc = incremental.last().expect("consumer typecheck result"); + let incremental_msgs = hard_diagnostic_messages(consumer_tc); + + assert!( + pipeline_msgs.is_empty() && incremental_msgs.is_empty(), + "incremental-surface path must match full pipeline (both clean), pipeline={pipeline_msgs:?} incremental={incremental_msgs:?}" + ); +} + +#[test] +fn variant_reexport_empty_surfaces_red_control() { + let sources = Rc::new(fixture_sources()); + let (modules, source_indices, intern_table) = resolved_module_graph(sources); + + let incremental = + typecheck_resolved_incremental(&modules, source_indices.clone(), intern_table.clone()); + assert!( + hard_diagnostic_messages(incremental.last().expect("consumer incremental typecheck")) + .is_empty(), + "incremental path must be clean before RED control" + ); + let module_index: Rc>> = incremental + .iter() + .map(|tc| { + let path = authored_name_at(source_indices.clone(), tc.typed.module.clone()); + (path, tc.typed.clone()) + }) + .fold(v1_rt::rc_empty_map(), |acc, (k, v)| { + v1_rt::rc_map_insert(acc, k, v) + }); + + let consumer = consumer_resolved(&modules, &source_indices); + let isolated = typecheck_module_isolated( + consumer.clone(), + module_index, + source_indices.clone(), + intern_table, + ); + let isolated_msgs = hard_diagnostic_messages(&isolated); + assert!( + isolated_msgs + .iter() + .any(|m| m.contains("'B'") || m.contains("unresolved type 'B'")), + "isolated typecheck (empty variant_surfaces) must RED on re-export arm import, got:\n{}", + isolated_msgs.join("\n") + ); +}