diff --git a/dag/gunbc/recurring_failure_mode/import_collides_with_a_kernel_name.dag b/dag/gunbc/recurring_failure_mode/import_collides_with_a_kernel_name.dag new file mode 100644 index 00000000000..c411672ed35 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/import_collides_with_a_kernel_name.dag @@ -0,0 +1,29 @@ +module gunbc.recurring_failure_mode.import_collides_with_a_kernel_name + +import std.types { NonEmptyStr } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data import_collides_with_a_kernel_name: RecurringFailureMode = RecurringFailureMode { + identity: "import_collides_with_a_kernel_name" as NonEmptyStr, + + receipts: [ + "**import collides with a kernel name** (INVALID STATE: a module authors `import M \{ Optional \}` where M declares a type named Optional that is not the kernel mint, while the kernel Optional (`std.optional`, minted by `v1.compiler.infer` `kernel_optional_type_node`) is also in the closure. The type environment's by-name overlay (`overlay_skips_kernel_name`: kernel wins) and the interned-identity lookup (`lookup_binding`: import can win) then type the same spelling two ways. The emitted module rendered `HeldChoice`, a leaked kernel type variable. Silent wrongness on the source path; rustc was the only wall.", + + "MEASURED by bright-swift-722 on gunbc#13454 (lane 2K-b). The collision declaration and its importer live under fixtures/ (`native_emission_controls_optional_collision`, `native_emission_controls_optional_importer`) and are compiled only as a fixture. A second Optional type must not sit in a source-root: leaf-keyed Optional typing then follows the last declarer in every floor closure.", + + "CHAIN RE-DERIVATION (§6b). The symptom is at emit (HeldChoice). The two lookups already disagree at resolve/infer. Unifying them under one precedence would pick one referent consistently and leave the other Optional type in the closure unused or mis-typed -- a second lie, not one rule. The earliest unjustified boundary is the authored import of a non-mint type that shares the mint's spelling. Refusal at that import is the construction.", + + "SCOPE OF THE WALL, from the census before the cut. Kernel-named IMPORTS are the ordinary corpus: String 3757, List 3294, Bool 3169, Int 1989, Present 997, Absent 965, Optional 372 (359 of them from `std.optional`). Refusing every kernel-named import would refuse the floor. TYPE DECLARATIONS of those names are few: Optional in the accepted corpus is `std.optional`; the colliding declaration is a fixture only; String is `v2.std.node` and `v2.std.text`; Int is `std.integer` and `v2.std.integer`. The wall fires only when the imported module DECLARES a type of that name AND a `kernel_mint_declaration_rows` row names a different DeclarationRef as the mint (module_path AND decl_name). A row for owner.Maybe does not exempt importing owner.Optional. Ambiguous mint rows refuse as KernelMintDeclarationAmbiguousAtImport, not as a guessed module. Dual-representation imports (`v2.std.text` String) stay with `carrier_by_spelling`.", + + "CURRENT RUNG: mechanically preventable. Discriminating RED: `test.claim.infer_kernel_import_collision_witness` `importing_a_non_mint_optional_refuses_at_the_import` (ImportCollidesWithKernelName at the import). Positive controls: a non-kernel name imported from the same collision module is accepted; importing the mint (`std.optional` Optional) compiles clean. The Maybe-row exemption hole is refused by `a_mint_row_for_owner_maybe_does_not_exempt_importing_owner_optional`. CEILING: structurally guaranteed -- an Accepted program cannot contain the colliding import. NEXT-RUNG TRIGGER: the same refusal is derived on the v2 resolve path for every kernel-mint spelling, so the two compilers cannot disagree on whether the import is writable.", + ], + + evidence: [ + DeclarationRef { module_path: "std.literal_elaboration", decl_name: "kernel_named_import_standing", field: WholeDeclaration }, + DeclarationRef { module_path: "v1.compiler.resolve", decl_name: "kernel_named_import_diags", field: WholeDeclaration }, + DeclarationRef { module_path: "v1.compiler.resolve", decl_name: "resolve_import", field: WholeDeclaration }, + DeclarationRef { module_path: "test.claim.infer_kernel_import_collision_witness", decl_name: "importing_a_non_mint_optional_refuses_at_the_import", field: WholeDeclaration }, + DeclarationRef { module_path: "gunbc.structural_realization_bindings", decl_name: "kernel_mint_declaration_rows", field: WholeDeclaration }, + ], +} diff --git a/dag/std/literal_elaboration.dag b/dag/std/literal_elaboration.dag index be820ba8efd..2fc0bb199f3 100644 --- a/dag/std/literal_elaboration.dag +++ b/dag/std/literal_elaboration.dag @@ -1,7 +1,7 @@ module std.literal_elaboration import std.types { List, String, Bool, Int, NonEmptyStr } -import std.decl_ref { DeclarationRef, declaration_ref_eq } +import std.decl_ref { DeclarationRef, decl_ref, declaration_ref_eq } import std.syntax { LiteralValue, LitStr, LitInt, LitFloat, LitBool, LitNull, LitSymbol } // THE EXPECTED-TYPE-DIRECTED LITERAL INTRODUCTION JUDGMENT, as a model (DESIGN section 4: one @@ -304,3 +304,33 @@ fn kernel_mint_ownership(rows: List, minted_name: String, KernelMintDeclarationAmbiguous { row_count: n } => KernelMintOwnershipAmbiguous { row_count: n } } } + +// WHETHER AN AUTHORED IMPORT OF A KERNEL-MINTED SPELLING IS THE MINT ITSELF. The exemption is the +// full DeclarationRef, not the module path: a row for owner.Maybe does not admit importing +// owner.Optional. Ambiguous mint rows are their own arm so the consumer cannot guess a module. +type KernelNamedImportStanding + = KernelNamedImportAdmitted + | KernelNamedImportCollides { kernel_declaration_module: String } + | KernelNamedImportMintAmbiguous { row_count: Int } + +fn kernel_named_import_standing( + imported_name: String, + import_path: String, + module_declares_type: Bool, + rows: List +) -> KernelNamedImportStanding { + if module_declares_type == false { + KernelNamedImportAdmitted + } else { + match kernel_mint_declaration_for(rows: rows, minted_name: imported_name) { + KernelMintDeclarationFound { declaration: d } => + if declaration_ref_eq(a: d, b: decl_ref(module_path: import_path, decl_name: imported_name)) { + KernelNamedImportAdmitted + } else { + KernelNamedImportCollides { kernel_declaration_module: d.module_path as String } + } + KernelMintDeclarationAbsent => KernelNamedImportAdmitted + KernelMintDeclarationAmbiguous { row_count: n } => KernelNamedImportMintAmbiguous { row_count: n } + } + } +} diff --git a/dag/test/claim/infer_kernel_import_collision_witness_test.dag b/dag/test/claim/infer_kernel_import_collision_witness_test.dag new file mode 100644 index 00000000000..cce91012739 --- /dev/null +++ b/dag/test/claim/infer_kernel_import_collision_witness_test.dag @@ -0,0 +1,189 @@ +module test.claim.infer_kernel_import_collision_witness + +import extdeps.filesystem.filesystem_io +import std.types { String, Bool, Int, List, NonEmptyStr } +import std.decl_ref { decl_ref, decl_field_ref } +import std.literal_elaboration { + KernelMintDeclaration, + kernel_named_import_standing, + KernelNamedImportAdmitted, + KernelNamedImportCollides, + KernelNamedImportMintAmbiguous, +} +import v2.std.live_tree { LiveTreeDisposition, ReadsLiveTree } +import tools.multi_module_compile_fixture { + MultiModuleCompileFixture, + MultiModuleCompileFixtureOutcome, + FixtureInstrumentRefused, + FixtureCompileRefused, + FixtureCompileCompleted, + FixtureSource, + fixture_source, + compile_fixture, + fixture_has_class +} +import gunbc.compile_diagnostic_census { CompileDiagnosticCensusRow } + +data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree + +// ONE RULE: an authored import of a type that shares a kernel-mint spelling, and is not the +// mint's own declaration, REFUSES at the import (v1.compiler.resolve resolve_import via +// kernel_named_import_standing and kernel_named_import_diags). Unifying the two lookups would still leave two +// Optional types in the closure; the kernel overlay would keep winning by name and the +// imported declaration would keep winning by interned identity. Refusal is the construction. +// +// THE RED BYTES ARE THE FILES. Both live under fixtures/ (not a source-root): a second +// Optional type in the accepted corpus is itself a leaf-name fork. This witness reads +// those files and hands the bytes to compile_fixture (review 76934). + +data collision_path: String = "fixtures/native_emission_controls_optional_collision.dag" +data importer_path: String = "fixtures/native_emission_controls_optional_importer.dag" + +fn file_text(path: String) -> String { + filesystem_read(path: path).content +} + +fn file_source(path: String) -> FixtureSource { + fixture_source(path: path, content: file_text(path: path)) +} + +// SAME MODULE PATH as the collision file, but no `import std.types` — compile_fixture +// does not supply that module. The module still DECLARES Optional so the control is +// "import a non-kernel name from a module that also declares the colliding type". +data collision_fn_source: String = "module gunbc.instruments.native_emission_controls_optional_collision\n\ntype Optional\n = CollisionWrapped \{ value: Int \}\n | CollisionEmpty\n\nfn collision_seven() -> Int \{\n 7\n\}\n" + +data non_kernel_import_source: String = "module probe_kernel_import_ok\n\nimport gunbc.instruments.native_emission_controls_optional_collision \{ collision_seven \}\n\nfn uses_fn() -> Int \{\n collision_seven()\n\}\n" + +data mint_import_source: String = "module probe_kernel_mint_import\n\nimport std.optional \{ Optional \}\n\nfn uses_mint(o: Optional) -> Int \{\n match o \{\n Present \{ value: v \} => v\n Absent => 0\n \}\n\}\n" + +data mint_decl_source: String = "module std.optional\n\ntype Optional\n = Absent\n | Present \{ value: T \}\n" + +data collision_class: String = "ImportCollidesWithKernelName" + +fn compile_importer() -> MultiModuleCompileFixtureOutcome { + compile_fixture(MultiModuleCompileFixture { + sources: [ + file_source(path: collision_path), + file_source(path: importer_path) + ], + entry: importer_path + }) +} + +fn compile_non_kernel_import() -> MultiModuleCompileFixtureOutcome { + compile_fixture(MultiModuleCompileFixture { + sources: [ + fixture_source(collision_path, collision_fn_source), + fixture_source("probe_kernel_import_ok.dag", non_kernel_import_source) + ], + entry: "probe_kernel_import_ok.dag" + }) +} + +fn compile_mint_import() -> MultiModuleCompileFixtureOutcome { + compile_fixture(MultiModuleCompileFixture { + sources: [ + fixture_source("std/optional.dag", mint_decl_source), + fixture_source("probe_kernel_mint_import.dag", mint_import_source) + ], + entry: "probe_kernel_mint_import.dag" + }) +} + +fn outcome_rows(o: MultiModuleCompileFixtureOutcome) -> List? { + match o { + FixtureCompileRefused { module_count: _, diagnostics: rows, source_digest: _, compiler_digest: _ } => Present { value: rows } + FixtureCompileCompleted { module_count: _, emitted_files: _, resolved_rust_functions: _, diagnostics: rows, source_digest: _, compiler_digest: _ } => Present { value: rows } + FixtureInstrumentRefused { cause: _ } => none + } +} + +fn outcome_clean(o: MultiModuleCompileFixtureOutcome) -> Bool { + match o { + FixtureCompileCompleted { module_count: n, emitted_files: files, resolved_rust_functions: _, diagnostics: rows, source_digest: _, compiler_digest: _ } => + count(rows) == 0 && count(files) > 0 && n > 0, + FixtureCompileRefused { module_count: _, diagnostics: _, source_digest: _, compiler_digest: _ } => false, + FixtureInstrumentRefused { cause: _ } => false + } +} + +test fn importing_a_non_mint_optional_refuses_at_the_import() -> Bool { + match outcome_rows(o: compile_importer()) { + Absent => false + Present { value: rows } => fixture_has_class(rows: rows, wanted: collision_class) + } +} + +test fn importing_a_non_kernel_name_from_the_same_module_is_accepted() -> Bool { + outcome_clean(o: compile_non_kernel_import()) +} + +test fn importing_the_kernel_mint_optional_is_accepted() -> Bool { + outcome_clean(o: compile_mint_import()) +} + +fn owner_maybe_mint_rows() -> List { + [KernelMintDeclaration { + minted_name: "Optional" as NonEmptyStr, + declaration: decl_ref(module_path: "owner", decl_name: "Maybe") + }] +} + +test fn a_mint_row_for_owner_maybe_does_not_exempt_importing_owner_optional() -> Bool { + match kernel_named_import_standing( + imported_name: "Optional", + import_path: "owner", + module_declares_type: true, + rows: owner_maybe_mint_rows() + ) { + KernelNamedImportCollides { kernel_declaration_module: m } => m == "owner" + KernelNamedImportAdmitted => false + KernelNamedImportMintAmbiguous { row_count: _ } => false + } +} + +test fn importing_the_mint_declaration_itself_is_admitted() -> Bool { + match kernel_named_import_standing( + imported_name: "Optional", + import_path: "std.optional", + module_declares_type: true, + rows: [KernelMintDeclaration { + minted_name: "Optional" as NonEmptyStr, + declaration: decl_ref(module_path: "std.optional", decl_name: "Optional") + }] + ) { + KernelNamedImportAdmitted => true + KernelNamedImportCollides { kernel_declaration_module: _ } => false + KernelNamedImportMintAmbiguous { row_count: _ } => false + } +} + +test fn a_mint_row_on_a_field_does_not_exempt_the_whole_declaration() -> Bool { + match kernel_named_import_standing( + imported_name: "Optional", + import_path: "owner", + module_declares_type: true, + rows: [KernelMintDeclaration { + minted_name: "Optional" as NonEmptyStr, + declaration: decl_field_ref(module_path: "owner", decl_name: "Optional", field_name: "value") + }] + ) { + KernelNamedImportCollides { kernel_declaration_module: m } => m == "owner" + KernelNamedImportAdmitted => false + KernelNamedImportMintAmbiguous { row_count: _ } => false + } +} + +test fn an_ambiguous_mint_row_set_is_its_own_refusal() -> Bool { + let doubled = concat(owner_maybe_mint_rows(), owner_maybe_mint_rows()) + match kernel_named_import_standing( + imported_name: "Optional", + import_path: "owner", + module_declares_type: true, + rows: doubled + ) { + KernelNamedImportMintAmbiguous { row_count: n } => n == 2 + KernelNamedImportAdmitted => false + KernelNamedImportCollides { kernel_declaration_module: _ } => false + } +} diff --git a/fixtures/native_emission_controls_optional_collision.dag b/fixtures/native_emission_controls_optional_collision.dag new file mode 100644 index 00000000000..5c82ca564c1 --- /dev/null +++ b/fixtures/native_emission_controls_optional_collision.dag @@ -0,0 +1,25 @@ +module gunbc.instruments.native_emission_controls_optional_collision + +import std.types { Int } + +// A NON-KERNEL COPRODUCT THAT HAPPENS TO BE NAMED Optional. Not a source-root +// module: a second Optional in the accepted corpus poisons leaf-keyed Optional +// typing in every floor closure. The RED compiles THIS file with the importer. +type Optional + = CollisionWrapped { value: Int } + | CollisionEmpty + +data collision_wrapped_row: Optional = CollisionWrapped { value: 7 } + +data collision_empty_row: Optional = CollisionEmpty + +fn collision_row_value(o: Optional) -> Int { + match o { + CollisionWrapped { value: v } => v + CollisionEmpty => 0 + } +} + +fn collision_seven() -> Int { + collision_row_value(o: CollisionWrapped { value: 7 }) +} diff --git a/fixtures/native_emission_controls_optional_importer.dag b/fixtures/native_emission_controls_optional_importer.dag new file mode 100644 index 00000000000..b6ff70a42be --- /dev/null +++ b/fixtures/native_emission_controls_optional_importer.dag @@ -0,0 +1,13 @@ +module gunbc.instruments.native_emission_controls_optional_importer + +import std.types { Int } +import gunbc.instruments.native_emission_controls_optional_collision { Optional } + +// THE DISCRIMINATING IMPORTER. Not ingested as corpus (fixtures/ is not a source root). +// test.claim.infer_kernel_import_collision_witness reads THIS file and compiles its bytes. +fn imported_optional_value(o: Optional) -> Int { + match o { + CollisionWrapped { value: v } => v + CollisionEmpty => 0 + } +} diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index 2bcaa4434fa..6058f871405 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -373,6 +373,8 @@ type CompilerDiagnostic = UnresolvedImport { module_path: String, importing_module: String, span: SourceSpan } | MissingExport { name: String, module_path: String, importing_module: String, span: SourceSpan } | ImportShadowedByLocalDefinition { name: String, module_path: String, importing_module: String, span: SourceSpan } + | ImportCollidesWithKernelName { name: String, module_path: String, importing_module: String, kernel_declaration_module: String, span: SourceSpan } + | KernelMintDeclarationAmbiguousAtImport { name: String, module_path: String, importing_module: String, row_count: Int, span: SourceSpan } | UnresolvedType { name: String, span: SourceSpan } | UnitVariantPhantomIdentityEvidenceUnavailable { name: String, span: SourceSpan } | TypeMismatch { expected: String, got: String, span: SourceSpan } @@ -604,6 +606,8 @@ fn diagnostic_to_span(d: CompilerDiagnostic) -> SourceSpan { UnresolvedImport { module_path: _, importing_module: _, span: s } => s MissingExport { name: _, module_path: _, importing_module: _, span: s } => s ImportShadowedByLocalDefinition { name: _, module_path: _, importing_module: _, span: s } => s + ImportCollidesWithKernelName { name: _, module_path: _, importing_module: _, kernel_declaration_module: _, span: s } => s + KernelMintDeclarationAmbiguousAtImport { name: _, module_path: _, importing_module: _, row_count: _, span: s } => s UnresolvedType { name: _, span: s } => s UnitVariantPhantomIdentityEvidenceUnavailable { name: _, span: s } => s TypeMismatch { expected: _, got: _, span: s } => s @@ -689,6 +693,8 @@ fn diagnostic_to_message(d: CompilerDiagnostic) -> String { UnresolvedImport { module_path: m, importing_module: i, span: _ } => concat("unresolved import: module '", m, "' not found (imported by '", i, "')") MissingExport { name: n, module_path: m, importing_module: i, span: _ } => concat("name '", n, "' not found in module '", m, "' (imported by '", i, "')") ImportShadowedByLocalDefinition { name: n, module_path: m, importing_module: i, span: _ } => concat("import of '", n, "' from module '", m, "' is discarded: '", i, "' also defines '", n, "' at module scope, and the LOCAL DEFINITION binds every bare use of the name. The import you wrote is not the binding you get. Qualify the call as '", m, ".", n, "(...)' to reach the imported one, or rename one of the two.") + ImportCollidesWithKernelName { name: n, module_path: m, importing_module: i, kernel_declaration_module: k, span: _ } => concat("import of '", n, "' from module '", m, "' (imported by '", i, "') collides with the kernel name whose mint is the declaration in '", k, "'. An authored import of a different type of that name is refused so the type environment and the field binding cannot type the same spelling two ways.") + KernelMintDeclarationAmbiguousAtImport { name: n, module_path: m, importing_module: i, row_count: c, span: _ } => concat("import of '", n, "' from module '", m, "' (imported by '", i, "') cannot be judged against the kernel mint: ", to_string(value: c), " kernel_mint_declaration_rows bind that minted name, so the owning declaration is undecidable and the import is refused rather than bound to a guessed module.") UnresolvedType { name: n, span: _ } => concat("unresolved type '", n, "'") UnitVariantPhantomIdentityEvidenceUnavailable { name: n, span: _ } => concat("unit-variant marker identity evidence unavailable for '", n, "'") TypeMismatch { expected: e, got: g, span: _ } => concat("type mismatch: expected '", e, "', got '", g, "'") @@ -851,6 +857,8 @@ fn diagnostic_disposition(d: CompilerDiagnostic) -> DiagnosticDisposition { UnresolvedImport { module_path: _, importing_module: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } MissingExport { name: _, module_path: _, importing_module: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } ImportShadowedByLocalDefinition { name: _, module_path: _, importing_module: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } + ImportCollidesWithKernelName { name: _, module_path: _, importing_module: _, kernel_declaration_module: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } + KernelMintDeclarationAmbiguousAtImport { name: _, module_path: _, importing_module: _, row_count: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } UnresolvedType { name: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } UnitVariantPhantomIdentityEvidenceUnavailable { name: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } TypeMismatch { expected: _, got: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } diff --git a/src/v1/03_resolve.dag b/src/v1/03_resolve.dag index 83c530c608d..6464668b80d 100644 --- a/src/v1/03_resolve.dag +++ b/src/v1/03_resolve.dag @@ -1,11 +1,22 @@ module v1.compiler.resolve -import std.types { kernel_type_set } +import std.types { kernel_type_set, SourceSpan } +import std.literal_elaboration { + kernel_named_import_standing, + KernelNamedImportStanding, + KernelNamedImportAdmitted, + KernelNamedImportCollides, + KernelNamedImportMintAmbiguous, +} +import gunbc.structural_realization_bindings { kernel_mint_declaration_rows } import v1.std.core { Node, Connective, Conj, Disj, NoConnective, ErrorNode, make_error_node, - UnresolvedImport, MissingExport, DuplicateModule, DuplicateDeclaration, CircularDependency, + UnresolvedImport, MissingExport, ImportShadowedByLocalDefinition, ImportCollidesWithKernelName, + KernelMintDeclarationAmbiguousAtImport, + DuplicateModule, DuplicateDeclaration, CircularDependency, + ModuleItemTypeDeclaration, no_span, module_node, import_node, import_is_all, import_specific_names_at, module_imports, module_items, NewlineIndex, authored_name_at, @@ -201,6 +212,95 @@ type ImportResolveResult { diagnostics: List } +// ONE RULE FOR A KERNEL-NAMED TYPE IMPORT. The type environment overlays kernel names above +// imports (`v1.compiler.infer` `overlay_skips_kernel_name`) while `lookup_binding` can still +// bind the imported declaration by interned identity. That is two answers for one spelling +// (DESIGN §5 silent wrongness; the Optional specimen emitted HeldChoice). +// Unifying the lookups would pick one lie consistently. Refusing the authored import that is +// not the mint's own declaration is the construction: one spelling, one referent, or a typed +// refusal at the import. The mint owner (`kernel_mint_declaration_rows`) may still be imported. +// Names with no mint row are not this wall -- they are the dual-representation population +// (`carrier_by_spelling` for String). Census before the cut: zero production modules import a +// non-mint type named Optional; 359 import Optional from std.optional and stay accepted. +fn module_type_declaration_names(module: Node, source_indices: Map) -> Map { + let type_names = module_items(n: module) + |> filter(item => item.module_item_kind == ModuleItemTypeDeclaration) + |> map(item => authored_name_at(source_indices: source_indices, node: item)) + type_names |> fold(init: empty_map(), f: fn(acc, name) { + map_insert(acc, name, true) + }) +} + +fn kernel_name_collision_diag( + name: String, + import_path: String, + importing_module: String, + kernel_declaration_module: String, + span: SourceSpan +) -> ErrorNode { + make_error_node(diagnostic: ImportCollidesWithKernelName { + name: name, + module_path: import_path, + importing_module: importing_module, + kernel_declaration_module: kernel_declaration_module, + span: span + }, module_name: importing_module) +} + +fn kernel_mint_ambiguous_import_diag( + name: String, + import_path: String, + importing_module: String, + row_count: Int, + span: SourceSpan +) -> ErrorNode { + make_error_node(diagnostic: KernelMintDeclarationAmbiguousAtImport { + name: name, + module_path: import_path, + importing_module: importing_module, + row_count: row_count, + span: span + }, module_name: importing_module) +} + +fn kernel_named_import_child_diags( + child: Node, + import_path: String, + type_decl_names: Map, + importing_module: String, + source_indices: Map +) -> List { + let name = authored_name_at(source_indices: source_indices, node: child) + kernel_named_import_diags( + standing: kernel_named_import_standing( + imported_name: name, + import_path: import_path, + module_declares_type: map_has(m: type_decl_names, key: name), + rows: kernel_mint_declaration_rows + ), + name: name, + import_path: import_path, + importing_module: importing_module, + span: child.span + ) +} + +fn kernel_named_import_diags( + standing: KernelNamedImportStanding, + name: String, + import_path: String, + importing_module: String, + span: SourceSpan +) -> List { + match standing { + KernelNamedImportAdmitted => [] + KernelNamedImportCollides { kernel_declaration_module: kernel_mod } => + [kernel_name_collision_diag(name: name, import_path: import_path, importing_module: importing_module, kernel_declaration_module: kernel_mod, span: span)] + KernelNamedImportMintAmbiguous { row_count: n } => + [kernel_mint_ambiguous_import_diag(name: name, import_path: import_path, importing_module: importing_module, row_count: n, span: span)] + } +} + fn resolve_import( import: Node, module_index: Map, @@ -243,6 +343,33 @@ fn resolve_import( make_error_node(diagnostic: MissingExport { name: authored_name_at(source_indices: source_indices, node: child), module_path: import_path, importing_module: importing_module, span: child.span }, module_name: importing_module) ) } + let type_decl_names = module_type_declaration_names(module: target_mod, source_indices: source_indices) + let kernel_collision_diags = if import_is_all(n: import) { + map_keys(type_decl_names) |> flat_map(name => + kernel_named_import_diags( + standing: kernel_named_import_standing( + imported_name: name, + import_path: import_path, + module_declares_type: true, + rows: kernel_mint_declaration_rows + ), + name: name, + import_path: import_path, + importing_module: importing_module, + span: import.span + ) + ) + } else { + import.children |> flat_map(child => + kernel_named_import_child_diags( + child: child, + import_path: import_path, + type_decl_names: type_decl_names, + importing_module: importing_module, + source_indices: source_indices + ) + ) + } ImportResolveResult { resolved: ResolvedImport { module_path: import_path, @@ -250,7 +377,7 @@ fn resolve_import( specific_names: import_specific_names_at(n: import, source_indices: source_indices), target_module: Present { value: target_mod } }, - diagnostics: concat(name_diags, shadow_diags) + diagnostics: concat(concat(name_diags, shadow_diags), kernel_collision_diags) } } } diff --git a/src/v1/stage0/src/cli_run/compile_clean.rs b/src/v1/stage0/src/cli_run/compile_clean.rs index 6f8d729362c..2e317d1ff94 100644 --- a/src/v1/stage0/src/cli_run/compile_clean.rs +++ b/src/v1/stage0/src/cli_run/compile_clean.rs @@ -1039,6 +1039,8 @@ pub fn compile_clean_diagnostic_class_specimen() -> Vec { UnresolvedImport { module_path: s(), importing_module: s(), span: no_span() }, MissingExport { name: s(), module_path: s(), importing_module: s(), span: no_span() }, ImportShadowedByLocalDefinition { name: s(), module_path: s(), importing_module: s(), span: no_span() }, + ImportCollidesWithKernelName { name: s(), module_path: s(), importing_module: s(), kernel_declaration_module: s(), span: no_span() }, + KernelMintDeclarationAmbiguousAtImport { name: s(), module_path: s(), importing_module: s(), row_count: 0, span: no_span() }, UnresolvedType { name: s(), span: no_span() }, TypeArgumentKindMismatch { type_name: s(), param_name: s(), kind_name: s(), supplied: s(), span: no_span() }, TypeParameterInValuePosition { name: s(), span: no_span() }, @@ -1527,6 +1529,10 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::ImportShadowedByLocalDefinition { .. } => { "ImportShadowedByLocalDefinition" } + CompilerDiagnostic::ImportCollidesWithKernelName { .. } => "ImportCollidesWithKernelName", + CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { .. } => { + "KernelMintDeclarationAmbiguousAtImport" + } CompilerDiagnostic::UnresolvedType { .. } => "UnresolvedType", CompilerDiagnostic::UnitVariantPhantomIdentityEvidenceUnavailable { .. } => { "UnitVariantPhantomIdentityEvidenceUnavailable" @@ -1636,6 +1642,8 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::UnresolvedImport { module_path, .. } => module_path.clone(), CompilerDiagnostic::MissingExport { name, .. } => name.clone(), CompilerDiagnostic::ImportShadowedByLocalDefinition { name, .. } => name.clone(), + CompilerDiagnostic::ImportCollidesWithKernelName { name, .. } => name.clone(), + CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { name, .. } => name.clone(), CompilerDiagnostic::UnresolvedType { name, .. } => name.clone(), CompilerDiagnostic::UnitVariantPhantomIdentityEvidenceUnavailable { name, .. } => { name.clone() diff --git a/src/v1/stage0/src/std_literal_elaboration.rs b/src/v1/stage0/src/std_literal_elaboration.rs index eadc0a8e83f..36ffd1bd1f1 100644 --- a/src/v1/stage0/src/std_literal_elaboration.rs +++ b/src/v1/stage0/src/std_literal_elaboration.rs @@ -4,13 +4,14 @@ use self::KernelGroundingLookup::*; use self::KernelMintDeclarationLookup::*; use self::KernelMintOwnership::*; +use self::KernelNamedImportStanding::*; use self::LiteralElaborationOutcome::*; use self::LiteralElaborationRefusal::*; use self::LiteralHomomorphismLookup::*; use self::LiteralSourceKind::*; use self::LiteralUnfolding::*; -pub use crate::std_decl_ref::declaration_ref_eq; pub use crate::std_decl_ref::DeclarationRef; +pub use crate::std_decl_ref::{decl_ref, declaration_ref_eq}; pub use crate::std_syntax::LiteralValue; use crate::std_syntax::LiteralValue::{LitBool, LitFloat, LitInt, LitNull, LitStr, LitSymbol}; pub use crate::std_types::{Bool, List, NonEmptyStr}; @@ -420,6 +421,48 @@ pub fn kernel_mint_ownership( } } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum KernelNamedImportStanding { + KernelNamedImportAdmitted, + KernelNamedImportCollides { kernel_declaration_module: String }, + KernelNamedImportMintAmbiguous { row_count: i64 }, +} + +pub fn kernel_named_import_standing( + imported_name: String, + import_path: String, + module_declares_type: bool, + rows: Rc>>, +) -> Rc { + if (module_declares_type.clone() == false) { + Rc::new(KernelNamedImportStanding::KernelNamedImportAdmitted) + } else { + match (*kernel_mint_declaration_for(rows.clone(), imported_name.clone())).clone() { + KernelMintDeclarationLookup::KernelMintDeclarationFound { declaration: d, .. } => { + if crate::std_decl_ref::declaration_ref_eq( + d.clone(), + crate::std_decl_ref::decl_ref(import_path.clone(), imported_name.clone()), + ) { + Rc::new(KernelNamedImportStanding::KernelNamedImportAdmitted) + } else { + Rc::new(KernelNamedImportStanding::KernelNamedImportCollides { + kernel_declaration_module: d.module_path.clone(), + }) + } + } + KernelMintDeclarationLookup::KernelMintDeclarationAbsent => { + Rc::new(KernelNamedImportStanding::KernelNamedImportAdmitted) + } + KernelMintDeclarationLookup::KernelMintDeclarationAmbiguous { + row_count: n, .. + } => Rc::new(KernelNamedImportStanding::KernelNamedImportMintAmbiguous { + row_count: n.clone(), + }), + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct KernelIntLiteral; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_compiler_resolve.rs b/src/v1/stage0/src/v1_compiler_resolve.rs index eef3badd9fb..49746eee141 100644 --- a/src/v1/stage0/src/v1_compiler_resolve.rs +++ b/src/v1/stage0/src/v1_compiler_resolve.rs @@ -1,19 +1,31 @@ // Generated by v1 compiler -- do not edit. // Source module: v1.compiler.resolve +pub use crate::gunbc_structural_realization_bindings::kernel_mint_declaration_rows; +pub use crate::std_literal_elaboration::kernel_named_import_standing; +pub use crate::std_literal_elaboration::KernelNamedImportStanding; +use crate::std_literal_elaboration::KernelNamedImportStanding::{ + KernelNamedImportAdmitted, KernelNamedImportCollides, KernelNamedImportMintAmbiguous, +}; pub use crate::std_occurrence_identity::{OccurrenceIndex, OccurrenceTransport}; pub use crate::std_types::kernel_type_set; +pub use crate::std_types::SourceSpan; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; use crate::v1_std_core::CompilerDiagnostic::{ - CircularDependency, DuplicateDeclaration, DuplicateModule, MissingExport, UnresolvedImport, + CircularDependency, DuplicateDeclaration, DuplicateModule, ImportCollidesWithKernelName, + ImportShadowedByLocalDefinition, KernelMintDeclarationAmbiguousAtImport, MissingExport, + UnresolvedImport, }; use crate::v1_std_core::Connective::{Conj, Disj, NoConnective}; +use crate::v1_std_core::ParsedModuleItemKind::ModuleItemTypeDeclaration; pub use crate::v1_std_core::{ authored_name_at, import_is_all, import_node, import_specific_names_at, make_error_node, module_imports, module_items, module_node, no_span, }; -pub use crate::v1_std_core::{CompilerDiagnostic, Connective, ErrorNode, NewlineIndex, Node}; +pub use crate::v1_std_core::{ + CompilerDiagnostic, Connective, ErrorNode, NewlineIndex, Node, ParsedModuleItemKind, +}; use crate::NonEmptyBTreeSet; use crate::NonEmptyVec; use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; @@ -370,6 +382,139 @@ pub struct ImportResolveResult { pub diagnostics: Rc>>, } +pub fn module_type_declaration_names( + module: Rc, + source_indices: Rc>>, +) -> Rc> { + { + let type_names = Rc::new({ + let mut __result = Vec::new(); + for item in Rc::new({ + let mut __result = Vec::new(); + for item in crate::v1_std_core::module_items(module.clone()) + .iter() + .cloned() + { + if (item.module_item_kind.clone() + == ParsedModuleItemKind::ModuleItemTypeDeclaration) + { + __result.push(item); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(crate::v1_std_core::authored_name_at( + source_indices.clone(), + item.clone(), + )); + } + __result + }); + type_names.iter().cloned().fold( + v1_rt::rc_empty_map::(), + |acc: Rc>, name: String| { + v1_rt::rc_map_insert(acc, name.clone(), true) + }, + ) + } +} + +pub fn kernel_name_collision_diag( + name: String, + import_path: String, + importing_module: String, + kernel_declaration_module: String, + span: Rc, +) -> Rc { + crate::v1_std_core::make_error_node( + Rc::new(CompilerDiagnostic::ImportCollidesWithKernelName { + name: name.clone(), + module_path: import_path.clone(), + importing_module: importing_module.clone(), + kernel_declaration_module: kernel_declaration_module.clone(), + span: span.clone(), + }), + importing_module.clone(), + ) +} + +pub fn kernel_mint_ambiguous_import_diag( + name: String, + import_path: String, + importing_module: String, + row_count: i64, + span: Rc, +) -> Rc { + crate::v1_std_core::make_error_node( + Rc::new(CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { + name: name.clone(), + module_path: import_path.clone(), + importing_module: importing_module.clone(), + row_count: row_count.clone(), + span: span.clone(), + }), + importing_module.clone(), + ) +} + +pub fn kernel_named_import_child_diags( + child: Rc, + import_path: String, + type_decl_names: Rc>, + importing_module: String, + source_indices: Rc>>, +) -> Rc>> { + { + let name = crate::v1_std_core::authored_name_at(source_indices.clone(), child.clone()); + kernel_named_import_diags( + crate::std_literal_elaboration::kernel_named_import_standing( + name.clone(), + import_path.clone(), + map_has(type_decl_names.clone(), name.clone()), + kernel_mint_declaration_rows(), + ), + name.clone(), + import_path.clone(), + importing_module.clone(), + child.span.clone(), + ) + } +} + +pub fn kernel_named_import_diags( + standing: Rc, + name: String, + import_path: String, + importing_module: String, + span: Rc, +) -> Rc>> { + match (*standing.clone()).clone() { + KernelNamedImportStanding::KernelNamedImportAdmitted => Rc::new(vec![]), + KernelNamedImportStanding::KernelNamedImportCollides { + kernel_declaration_module: kernel_mod, + .. + } => Rc::new(vec![kernel_name_collision_diag( + name.clone(), + import_path.clone(), + importing_module.clone(), + kernel_mod.clone(), + span.clone(), + )]), + KernelNamedImportStanding::KernelNamedImportMintAmbiguous { row_count: n, .. } => { + Rc::new(vec![kernel_mint_ambiguous_import_diag( + name.clone(), + import_path.clone(), + importing_module.clone(), + n.clone(), + span.clone(), + )]) + } + } +} + pub fn resolve_import( import: Rc, module_index: Rc>>, @@ -489,6 +634,50 @@ pub fn resolve_import( __result }) }; + let type_decl_names = + module_type_declaration_names(target_mod.clone(), source_indices.clone()); + let kernel_collision_diags = if crate::v1_std_core::import_is_all(import.clone()) { + Rc::new({ + let mut __result = Vec::new(); + for name in Rc::new(v1_rt::map_keys(&type_decl_names)).iter().cloned() { + __result.extend( + (*kernel_named_import_diags( + crate::std_literal_elaboration::kernel_named_import_standing( + name.clone(), + import_path.clone(), + true, + kernel_mint_declaration_rows(), + ), + name.clone(), + import_path.clone(), + importing_module.clone(), + import.span.clone(), + )) + .iter() + .cloned(), + ); + } + __result + }) + } else { + Rc::new({ + let mut __result = Vec::new(); + for child in import.children.clone().iter().cloned() { + __result.extend( + (*kernel_named_import_child_diags( + child.clone(), + import_path.clone(), + type_decl_names.clone(), + importing_module.clone(), + source_indices.clone(), + )) + .iter() + .cloned(), + ); + } + __result + }) + }; Rc::new(ImportResolveResult { resolved: Rc::new(ResolvedImport { module_path: import_path.clone(), @@ -499,7 +688,10 @@ pub fn resolve_import( ), target_module: Some(target_mod.clone()), }), - diagnostics: v1_rt::concat(name_diags.clone(), shadow_diags.clone()), + diagnostics: v1_rt::concat( + v1_rt::concat(name_diags.clone(), shadow_diags.clone()), + kernel_collision_diags.clone(), + ), }) } } diff --git a/src/v1/stage0/src/v1_std_core.rs b/src/v1/stage0/src/v1_std_core.rs index 5c05b25fcc5..1bdcfcb9b05 100644 --- a/src/v1/stage0/src/v1_std_core.rs +++ b/src/v1/stage0/src/v1_std_core.rs @@ -576,6 +576,20 @@ pub enum CompilerDiagnostic { importing_module: String, span: Rc, }, + ImportCollidesWithKernelName { + name: String, + module_path: String, + importing_module: String, + kernel_declaration_module: String, + span: Rc, + }, + KernelMintDeclarationAmbiguousAtImport { + name: String, + module_path: String, + importing_module: String, + row_count: i64, + span: Rc, + }, UnresolvedType { name: String, span: Rc, @@ -1004,6 +1018,8 @@ pub fn diagnostic_to_span(d: Rc) -> Rc { CompilerDiagnostic::UnresolvedImport { span: s, .. } => s.clone(), CompilerDiagnostic::MissingExport { span: s, .. } => s.clone(), CompilerDiagnostic::ImportShadowedByLocalDefinition { span: s, .. } => s.clone(), + CompilerDiagnostic::ImportCollidesWithKernelName { span: s, .. } => s.clone(), + CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { span: s, .. } => s.clone(), CompilerDiagnostic::UnresolvedType { span: s, .. } => s.clone(), CompilerDiagnostic::UnitVariantPhantomIdentityEvidenceUnavailable { span: s, .. } => { s.clone() @@ -1087,6 +1103,8 @@ pub fn diagnostic_to_message(d: Rc) -> String { CompilerDiagnostic::UnresolvedImport { module_path: m, importing_module: i, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("unresolved import: module '".to_string(), m.clone()), "' not found (imported by '".to_string()), i.clone()), "')".to_string()), CompilerDiagnostic::MissingExport { name: n, module_path: m, importing_module: i, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("name '".to_string(), n.clone()), "' not found in module '".to_string()), m.clone()), "' (imported by '".to_string()), i.clone()), "')".to_string()), CompilerDiagnostic::ImportShadowedByLocalDefinition { name: n, module_path: m, importing_module: i, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("import of '".to_string(), n.clone()), "' from module '".to_string()), m.clone()), "' is discarded: '".to_string()), i.clone()), "' also defines '".to_string()), n.clone()), "' at module scope, and the LOCAL DEFINITION binds every bare use of the name. The import you wrote is not the binding you get. Qualify the call as '".to_string()), m.clone()), ".".to_string()), n.clone()), "(...)' to reach the imported one, or rename one of the two.".to_string()), + CompilerDiagnostic::ImportCollidesWithKernelName { name: n, module_path: m, importing_module: i, kernel_declaration_module: k, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("import of '".to_string(), n.clone()), "' from module '".to_string()), m.clone()), "' (imported by '".to_string()), i.clone()), "') collides with the kernel name whose mint is the declaration in '".to_string()), k.clone()), "'. An authored import of a different type of that name is refused so the type environment and the field binding cannot type the same spelling two ways.".to_string()), + CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { name: n, module_path: m, importing_module: i, row_count: c, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("import of '".to_string(), n.clone()), "' from module '".to_string()), m.clone()), "' (imported by '".to_string()), i.clone()), "') cannot be judged against the kernel mint: ".to_string()), (c.clone()).to_string()), " kernel_mint_declaration_rows bind that minted name, so the owning declaration is undecidable and the import is refused rather than bound to a guessed module.".to_string()), CompilerDiagnostic::UnresolvedType { name: n, .. } => v1_rt::concat(v1_rt::concat("unresolved type '".to_string(), n.clone()), "'".to_string()), CompilerDiagnostic::UnitVariantPhantomIdentityEvidenceUnavailable { name: n, .. } => v1_rt::concat(v1_rt::concat("unit-variant marker identity evidence unavailable for '".to_string(), n.clone()), "'".to_string()), CompilerDiagnostic::TypeMismatch { expected: e, got: g, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("type mismatch: expected '".to_string(), e.clone()), "', got '".to_string()), g.clone()), "'".to_string()), @@ -1206,6 +1224,14 @@ pub fn diagnostic_disposition(d: Rc) -> Rc Rc::new(DiagnosticDisposition { severity: DiagnosticSeverity::SeverityError, gate: Rc::new(DiagnosticGateDisposition::GateBlocking), +}), + CompilerDiagnostic::ImportCollidesWithKernelName { .. } => Rc::new(DiagnosticDisposition { + severity: DiagnosticSeverity::SeverityError, + gate: Rc::new(DiagnosticGateDisposition::GateBlocking), +}), + CompilerDiagnostic::KernelMintDeclarationAmbiguousAtImport { .. } => Rc::new(DiagnosticDisposition { + severity: DiagnosticSeverity::SeverityError, + gate: Rc::new(DiagnosticGateDisposition::GateBlocking), }), CompilerDiagnostic::UnresolvedType { .. } => Rc::new(DiagnosticDisposition { severity: DiagnosticSeverity::SeverityError,