diff --git a/dag/test/claim/callable_candidate_ambiguity_witness_test.dag b/dag/test/claim/callable_candidate_ambiguity_witness_test.dag new file mode 100644 index 00000000000..fa8cf5f107c --- /dev/null +++ b/dag/test/claim/callable_candidate_ambiguity_witness_test.dag @@ -0,0 +1,153 @@ +module test.claim.callable_candidate_ambiguity_witness + +import std.types { String, Bool, Int } +import v2.std.optional { Present, Absent } +import gunbc.compile_diagnostic_census { CompileDiagnosticCensus } +import gunbc.guarantee_probe_corpus { census_blocking_count_for_class } +import gunbc.compile_diagnostic_census { census_blocking_rows, census_total_count, CensusObserved, CensusNotRunnable } + +// A CALLABLE NAME WITH TWO AUTHORITIES REFUSES, AND ONE WITH A LEXICAL OWNER STILL RESOLVES. +// +// THE INCIDENT THIS PINS (gap-analysis row 30). `map_get` names two callables: the registered +// builtin (returning Optional) and `v2.std.collection.map_get` (returning +// Outcome>). Which one a bare call meant was decided by whether the CONSUMER'S +// TRANSITIVE IMPORT CLOSURE happened to reach the declaration -- so one unrelated import edge +// added to `extdeps.git` moved two unrelated modules' closures and silently re-typechecked three +// untouched files into six non-exhaustive-match diagnostics. Forty-six files carry the same bare +// spelling; three had fired. Nothing observed the closure, in either direction. +// +// WHY THE THREE-STATE OUTCOME BEING PRESENT PROVED NOTHING, which is why this file exists rather +// than a grep. `v1.compiler.infer_sigs` has declared `FuncSigAmbiguous { candidates }` all along, +// documented as never falling through to a fallback -- and it could not describe this collision +// for two independent reasons: the candidate set was never assembled (the environment was asked +// first and the builtin surface only on the unresolved arm, so the two were never co-candidates), +// and where ambiguity WAS constructed one projection mapped it onto the same `Absent` that means +// "no such signature". A wall that is named, typed and documented is still not a wall. +// +// THE TRIPLE IS THE PROOF, and it discriminates the DEFECT from the STORM. All three compile a +// source through the REAL acceptance path (`compile_dag_diagnostic_census` runs the production +// resolve/typecheck over the live source roots), and they differ in exactly the fact under test: +// +// RED -- THE INCIDENT'S ACTUAL SHAPE. A module that names NO authority for the callable and +// reaches a declaration only transitively, through an import whose own closure happens +// to contain it. Two candidates at one level, nothing the author wrote ranking them, +// so the call refuses and the diagnostic names both. +// GREEN 1 -- THE SAME SPELLING, WITH THE AUTHORITY NAMED. `import v2.std.collection { map_get }` +// and then a bare call. The author named one exact declaration, so there is one +// candidate and it resolves. This control is not decoration: sixteen modules in +// dag/ and src/v2 do exactly this, among them 02_parse, 03_ingest, 03_resolve and +// target_model, and a wall without it refuses the v2 compiler core. +// GREEN 2 -- THE SAME SPELLING, DECLARED BY THE CALLER ITSELF. Lexical scope: the nearest +// binding wins, exactly as it always has. +// +// WHY THE RED HAD TO BE REBUILT, recorded because the first version of this file was the failure +// it exists to catch. It used the GREEN 1 source as its RED -- a listed import -- and asserted a +// refusal. That is not the incident: `dag/extdeps/git/object_store.dag` at ac9e010a83~1 names +// `v2.std.collection` zero times. A witness that certifies a wall while testing a shape the +// defect never had is worse than no witness, because it goes green and reads as coverage; this +// one would have certified refusing the compiler core as correct behaviour. + +fn census_of(source: String) -> CompileDiagnosticCensus { + compile_dag_diagnostic_census(source) +} + +fn blocking_count_for(source: String, wanted: String) -> Int { + match census_blocking_count_for_class(census: census_of(source: source), wanted: wanted) { + Present { value: n } => n + Absent => 0 - 1 + } +} + +// NOT-RUNNABLE IS NOT ZERO, which is why the Absent arm answers -1 rather than 0. +// +// A census that could not run has observed NOTHING, and reporting that as "no diagnostics" would +// let a broken harness satisfy the green control while making the red unsatisfiable -- the +// empty-observation narrow, one layer up from the class this file is about. -1 fails in both +// directions: it is neither the 0 a green asserts nor the positive count a red asserts, so a +// harness that stops working cannot masquerade as either verdict. +fn blocking_ambiguous_reference_count(source: String) -> Int { + match census_blocking_count_for_class(census: census_of(source: source), wanted: "AmbiguousReference") { + Present { value: n } => n + Absent => 0 - 1 + } +} + + +// A BROKEN FIXTURE MUST NOT LOOK LIKE A QUIET WALL. The first RED imported `PosixUserName`, +// which extdeps.posix.identity does not export: the source failed for an unrelated reason, +// produced zero AmbiguousReference rows, and the assertion read `false` -- indistinguishable from +// "the wall did not fire". Both were true at once, which is why the pair could not diagnose +// itself. The assertions below are per-class on the two classes this file is about, and the +// counter answers 0 - 1 on a census that could not run, so a broken harness fails in both +// directions rather than passing by silence. + +// THE BOUNDARY CONTROL, and it is the row that keeps this file's prose honest. +// +// A listed import removes the implicit BUILTIN co-candidate and does nothing else. It carries +// NAME MEMBERSHIP, not selected declaration identity -- `authored_import_names` is a +// Map, so it cannot say WHICH declaration the author meant -- and the remaining +// DECLARED population still undergoes exact cardinality admission. +// +// This source names `int_max` in a listed import from `v2.std.spine`, whose own closure reaches +// `std.realization_width`, which declares `int_max` too. Two declarations answer at one level, +// nothing the author wrote ranks them, and the call REFUSES. No builtin is involved at all -- +// `int_max` has no registry row -- which is what makes this row ISOLATE the claim rather than +// restate the RED: it is about the declared population surviving a listed import, not about the +// builtin being suppressed by one. +// +// If anyone later reads level 1a as "the explicitly imported declaration excludes every +// transitive homonym", this control goes red, because that rule needs an exact-binding carrier +// that does not exist. A green here would mean the claim had been strengthened past its +// mechanism without anyone noticing. +// +// (Its first version imported `v1.compiler.emit_core_support`, a SEED module. The census +// resolves against the witness roots ["dag", "src/v2"], so that import could never resolve, no +// `to_string` candidate existed, and the assertion read false for a reason unrelated to the +// boundary -- the same broken-fixture class as the RED's `PosixUserName`, committed inside the +// control built to prevent a different mistake.) +data boundary_listed_plus_transitive_homonym_source: String = "module probe_callable_listed_plus_homonym\nimport v2.std.spine { int_max }\nimport std.types { Int }\nfn widest() -> Int {\n int_max(a: 3, b: 7)\n}\n" + +data red_transitive_reach_source: String = "module probe_callable_transitive_reach\nimport extdeps.posix.identity { PosixUserId }\nimport std.types { String, Int, Map }\nfn reads(m: Map) -> Int {\n match map_get(m, \"k\") {\n Present { value: v } => v\n Absent => 0\n }\n}\n" + +data green_named_authority_source: String = "module probe_callable_named_authority\nimport v2.std.collection { map_get }\nimport std.types { String, Int, Map }\nfn reads(m: Map) -> Int {\n match map_get(m, \"k\") {\n Accepted { value: inner } => 0\n Rejected { diagnostics: _ } => 0\n }\n}\n" + +data green_own_declaration_source: String = "module probe_callable_own_declaration\nimport std.types { String, Int, Map, Bool }\nfn map_has(m: Map, key: String) -> Bool {\n match map_get(m, key) {\n Present { value: v } => v\n Absent => false\n }\n}\nfn asks(m: Map) -> Bool { map_has(m: m, key: \"k\") }\n" + +test fn a_bare_call_whose_only_declarer_is_reached_transitively_refuses() -> Bool { + blocking_ambiguous_reference_count(source: red_transitive_reach_source) >= 1 +} + +// THE REFUSAL REPLACES THE SYMPTOM, IT DOES NOT ADD TO IT. Before the candidate set was +// collected, this same source produced VariantNotFound/Present, VariantNotFound/Absent and +// NonExhaustiveMatch -- three DOWNSTREAM rows describing the damage, each pointing an author at +// a match arm that was never the problem, because the Outcome-returning declaration had won +// silently. The wall is not a fourth row on top of those: it is the one row at the call, and +// they are gone. Asserting their absence is what distinguishes a diagnosis from a check. +test fn the_refusal_replaces_the_downstream_symptom_rather_than_adding_to_it() -> Bool { + blocking_count_for(source: red_transitive_reach_source, wanted: "NonExhaustiveMatch") == 0 +} + +// PER-CLASS, NOT A BLANKET BLOCKING COUNT. An earlier revision asserted that the GREEN sources +// carry NO blocking diagnostic at all. Measured, that read false for both -- the census compiles +// a synthetic source against the live witness roots, so an unrelated UnresolvedType from the +// harness closure lands in the count. A check whose RED is produced by something it does not +// measure reports a wall failure when the harness sneezes; these assert the two classes this +// file is actually about. +test fn neither_green_source_refuses_and_neither_mis_resolves() -> Bool { + blocking_ambiguous_reference_count(source: green_named_authority_source) == 0 + && blocking_ambiguous_reference_count(source: green_own_declaration_source) == 0 + && blocking_count_for(source: green_named_authority_source, wanted: "NonExhaustiveMatch") == 0 + && blocking_count_for(source: green_own_declaration_source, wanted: "NonExhaustiveMatch") == 0 +} + +test fn a_bare_call_whose_authority_the_author_named_still_resolves() -> Bool { + blocking_ambiguous_reference_count(source: green_named_authority_source) == 0 +} + +test fn a_listed_import_does_not_exclude_a_transitively_reached_homonym() -> Bool { + blocking_ambiguous_reference_count(source: boundary_listed_plus_transitive_homonym_source) >= 1 +} + +test fn a_module_calling_its_own_declaration_of_a_builtin_name_still_resolves() -> Bool { + blocking_ambiguous_reference_count(source: green_own_declaration_source) == 0 +} diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 36aee5f1245..eac4c75c6ac 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -28,6 +28,7 @@ type TypeEnv { source_indices: Map intern_table: InternTable source_visible_names: Map + authored_import_names: Map symbol_index: SymbolIndex module_path: String } @@ -100,7 +101,7 @@ fn empty_type_env() -> TypeEnv { inductive_fields: empty_map(), source_indices: empty_map(), intern_table: empty_intern_table(), - source_visible_names: empty_map(), + source_visible_names: empty_map(), authored_import_names: empty_map(), symbol_index: empty_symbol_index() } } @@ -1081,6 +1082,7 @@ fn env_with_type_variable_bindings(env: TypeEnv, tp_names: List) -> Type source_indices: e.source_indices, intern_table: e.intern_table, source_visible_names: map_insert(e.source_visible_names, tp_name, true), + authored_import_names: e.authored_import_names, symbol_index: e.symbol_index } ) diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 21edb37cbcf..147d2a7c7fd 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -149,7 +149,12 @@ import v1.compiler.infer_resolve { import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv, ResolveFuncSigsResult, resolve_func_sigs, flatten_parent_envs, - FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous + FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous, + CallableCandidate, CallableIdentity, DeclaredCallable, BuiltinCallable, + callable_identity_label, callable_candidate_labels, + DerivedCalleeSig, DerivedFromSig, NoDerivableSig, + NoDerivableSigReason, DerivationSigUnresolved, DerivationSigAmbiguous, + func_sig_for_derivation } import v1.compiler.infer_emit_info { TypeRepr, StructRepr, EnumRepr, @@ -187,7 +192,7 @@ import v1.compiler.infer_lookup { field_summary_for_type, lookup_field_type_node, lookup_coproduct_common_field_node, product_field_result_type, map_value_type_in_env, map_key_type_in_env, set_element_type_in_env, - lookup_in_scope, lookup_func_sig, func_sig_if_resolved, + lookup_in_scope, lookup_func_sig, global_bare_callable_node, func_decl_binding_for_call, ConstructorDeclarationLookup, ExactConstructorDeclaration, NotAdmissionBearingReference, AdmissionBearingDeclarationUnavailable, @@ -3669,6 +3674,13 @@ fn qualified_value_projection(texpr: Node, scope: InferScope, span: SourceSpan) } } +// ONE ARM OF `infer_expr_body` BELOW IS UNREACHABLE AND SPELLED ANYWAY: at the ExprCall +// signature lookup, the enclosing match refuses FuncSigAmbiguous before that body runs, so +// the collapse cannot happen there -- but the arm is written out instead of wildcarded so +// that a new FuncSigLookup arm is a compile error at this seam rather than a silent Absent, +// which is exactly how the deleted `func_sig_if_resolved` came to erase the ambiguity in +// the first place. + fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResult { match texpr.expr_data { ExprLiteral { value: lit } => @@ -3741,7 +3753,7 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu )) } FuncSigAmbiguous { candidates: ambiguous_fn_candidates } => - ambiguous_reference_refusal(name: name, candidates: ambiguous_fn_candidates, span: span, scope: scope) + ambiguous_reference_refusal(name: name, candidates: callable_candidate_labels(candidates: ambiguous_fn_candidates), span: span, scope: scope) FuncSigUnresolved => match lookup_binding_by_name(env: scope.type_env, name: name) { Present { value: gbinding } => @@ -3908,12 +3920,16 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu let call_sig_lookup = body_shadow_aware_func_sig(scope: scope, func_name: func_name) match call_sig_lookup { FuncSigAmbiguous { candidates: ambiguous_fn_candidates } => - ambiguous_reference_refusal(name: func_name, candidates: ambiguous_fn_candidates, span: span, scope: scope) + ambiguous_reference_refusal(name: func_name, candidates: callable_candidate_labels(candidates: ambiguous_fn_candidates), span: span, scope: scope) _ => match constructor_reference_admission_unshadowed(callee_name: func_name, span: span, scope: scope) { Present { value: refusal } => refusal Absent => - let sig = func_sig_if_resolved(lookup: call_sig_lookup) + let sig = match call_sig_lookup { + FuncSigResolved { sig: resolved_call_sig } => Present { value: resolved_call_sig } + FuncSigUnresolved => Absent + FuncSigAmbiguous { candidates: _ } => Absent + } let sig_params = match sig { Present { value: s } => s.params Absent => [] @@ -6044,8 +6060,8 @@ fn build_per_field_for_let(val: Node, binding_name: String, ctx: DescentContext) match val.expr_data { ExprCall { call_semantics: _, descent_evidence: _ } => let callee = expr_call_func_at(texpr: val, source_indices: ctx.type_env.source_indices) - match func_sig_if_resolved(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { - Present { value: sig } => + match func_sig_for_derivation(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { + DerivedFromSig { sig: sig } => if sig.output_provenance |> count > 1 { let return_type = sig.inferred let field_map = return_type.children |> enumerate |> fold(init: empty_map(), f: (acc, pair) => @@ -6058,12 +6074,12 @@ fn build_per_field_for_let(val: Node, binding_name: String, ctx: DescentContext) map_insert(ctx.per_field_vars, binding_name, field_map) } else { ctx.per_field_vars } } else { ctx.per_field_vars } - Absent => ctx.per_field_vars + NoDerivableSig { reason: _ } => ctx.per_field_vars } ExprMethodCall { method_semantics: _ } => let callee = expr_method_name_at(texpr: val, source_indices: ctx.type_env.source_indices) - match func_sig_if_resolved(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { - Present { value: sig } => + match func_sig_for_derivation(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { + DerivedFromSig { sig: sig } => if sig.output_provenance |> count > 1 { let return_type = sig.inferred let field_map = return_type.children |> enumerate |> fold(init: empty_map(), f: (acc, pair) => @@ -6076,7 +6092,7 @@ fn build_per_field_for_let(val: Node, binding_name: String, ctx: DescentContext) map_insert(ctx.per_field_vars, binding_name, field_map) } else { ctx.per_field_vars } } else { ctx.per_field_vars } - Absent => ctx.per_field_vars + NoDerivableSig { reason: _ } => ctx.per_field_vars } _ => ctx.per_field_vars } @@ -6212,14 +6228,14 @@ fn classify_let_value(val: Node, ctx: DescentContext) -> SubValueRelation? { } ExprCall { call_semantics: _, descent_evidence: _ } => let callee = expr_call_func_at(texpr: val, source_indices: ctx.type_env.source_indices) - let from_provenance = match func_sig_if_resolved(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { - Present { value: sig } => + let from_provenance = match func_sig_for_derivation(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { + DerivedFromSig { sig: sig } => match sig.output_provenance |> first { Present { value: param_map } => classify_call_via_provenance(call: val, param_map: param_map, ctx: ctx) Absent => none } - Absent => none + NoDerivableSig { reason: _ } => none } match from_provenance { Present { value: _ } => from_provenance @@ -6479,14 +6495,14 @@ fn classify_argument(arg_expr: Node, param_name: String, ctx: DescentContext) -> ArithmeticDescent { param: _, factor: _ } => inner_rel PreservedValue => let callee = expr_call_func_at(texpr: arg_expr, source_indices: ctx.type_env.source_indices) - let from_provenance = match func_sig_if_resolved(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { - Present { value: sig } => + let from_provenance = match func_sig_for_derivation(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { + DerivedFromSig { sig: sig } => match sig.output_provenance |> first { Present { value: param_map } => classify_call_via_provenance(call: arg_expr, param_map: param_map, ctx: ctx) Absent => none } - Absent => none + NoDerivableSig { reason: _ } => none } match from_provenance { Present { value: prov_rel } => prov_rel @@ -6802,11 +6818,11 @@ fn annotate_descent(body: Node, ctx: DescentContext) -> Node { let scrut_variant_sig = match scrut.expr_data { ExprCall { call_semantics: _, descent_evidence: _ } => let callee = expr_call_func_at(texpr: scrut, source_indices: ctx.type_env.source_indices) - match func_sig_if_resolved(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { - Present { value: sig } => + match func_sig_for_derivation(lookup: lookup_func_sig(func_env: ctx.func_env, type_env: ctx.type_env, name: callee)) { + DerivedFromSig { sig: sig } => if map_keys(sig.variant_provenance) |> count > 0 { Present { value: sig } } else { none } - Absent => none + NoDerivableSig { reason: _ } => none } _ => none } @@ -7410,15 +7426,15 @@ fn method_call_args_by_name(call: Node, func_env: ResolvedFuncEnv, type_env: Typ let mname = expr_method_name_at(texpr: call, source_indices: source_indices) let recv = method_receiver(texpr: call) let mc_args = method_arg_nodes(texpr: call) - let base = match func_sig_if_resolved(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: mname)) { - Present { value: sig } => + let base = match func_sig_for_derivation(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: mname)) { + DerivedFromSig { sig: sig } => match sig.params |> first { Present { value: first_p } => let first_pname = param_node_name_at(n: first_p, source_indices: source_indices) if first_pname != "" { map_insert(empty_map(), first_pname, recv) } else { empty_map() } Absent => empty_map() } - Absent => empty_map() + NoDerivableSig { reason: _ } => empty_map() } fold(mc_args, init: base, f: (acc, arg_node) => match arg_name_at(n: arg_node, source_indices: source_indices) { @@ -7429,8 +7445,8 @@ fn method_call_args_by_name(call: Node, func_env: ResolvedFuncEnv, type_env: Typ } fn compose_callee_provenance(callee: String, arg_by_name: Map, param_names: List, param_types: Map, type_env: TypeEnv, func_env: ResolvedFuncEnv, let_prov: Map>) -> Map { - match func_sig_if_resolved(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: callee)) { - Present { value: sig } => + match func_sig_for_derivation(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: callee)) { + DerivedFromSig { sig: sig } => match sig.output_provenance |> first { Present { value: callee_prov } => fold(callee_prov |> map_keys, init: empty_map(), f: (acc, callee_pname) => @@ -7459,7 +7475,7 @@ fn compose_callee_provenance(callee: String, arg_by_name: Map, par ) Absent => empty_map() } - Absent => empty_map() + NoDerivableSig { reason: _ } => empty_map() } } @@ -7680,8 +7696,8 @@ fn collect_variant_constructors(body: Node, parent_enum: String, param_names: Li } ExprCall { call_semantics: _, descent_evidence: _ } => let callee = expr_call_func_at(texpr: body, source_indices: type_env.source_indices) - match func_sig_if_resolved(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: callee)) { - Present { value: sig } => + match func_sig_for_derivation(lookup: lookup_func_sig(func_env: func_env, type_env: type_env, name: callee)) { + DerivedFromSig { sig: sig } => if authored_name_at(source_indices: type_env.source_indices, node: sig.inferred) == parent_enum && (map_keys(sig.variant_provenance) |> count > 0) { let call_args = call_args_by_name(call: body, source_indices: type_env.source_indices) @@ -7707,7 +7723,7 @@ fn collect_variant_constructors(body: Node, parent_enum: String, param_names: Li } ) } else { acc } - Absent => acc + NoDerivableSig { reason: _ } => acc } ExprMatch => match_arm_nodes(texpr: body) |> fold(init: acc, f: (a, arm) => @@ -8551,7 +8567,7 @@ fn type_env_for_import(module_path: String, parent_env: TypeEnv) -> TypeEnv { inductive_fields: parent_env.inductive_fields, source_indices: parent_env.source_indices, intern_table: parent_env.intern_table, - source_visible_names: parent_env.source_visible_names, + source_visible_names: parent_env.source_visible_names, authored_import_names: parent_env.authored_import_names, symbol_index: parent_env.symbol_index } } else { parent_env } @@ -8571,7 +8587,7 @@ fn interface_env_for_import(module_path: String, parent_env: TypeEnv) -> TypeEnv inductive_fields: filtered.inductive_fields, source_indices: filtered.source_indices, intern_table: filtered.intern_table, - source_visible_names: filtered.source_visible_names, + source_visible_names: filtered.source_visible_names, authored_import_names: filtered.authored_import_names, symbol_index: filtered.symbol_index } } @@ -8591,7 +8607,7 @@ fn interface_env_surface(env: TypeEnv) -> TypeEnv { inductive_fields: env.inductive_fields, source_indices: env.source_indices, intern_table: env.intern_table, - source_visible_names: env.source_visible_names, + source_visible_names: env.source_visible_names, authored_import_names: env.authored_import_names, symbol_index: env.symbol_index } } @@ -9241,7 +9257,7 @@ fn census_fn_sig_env(census: SymbolIndex, module_path: String, tp_names: List ) } +// THE AUTHORED HALF OF THIS FUNCTION'S VISIBILITY MAPS, KEPT SEPARATE BECAUSE THE UNION DESTROYS IT. +// `build_type_env` folds BOTH import arms into one `source_visible_names` map: an is_all import +// contributes every name in the imported module's interface AND every name that module itself +// acquired through ancestry -- so a bare `import v2.std.collection` puts `map_get` in the map +// without the author ever writing the word, and the ancestry half contributes names +// TRANSITIVELY, which is the closure-supply case gap-analysis row 30 is about, arriving through +// a set whose name suggests the opposite. +// +// Membership in source_visible_names is therefore NECESSARY for "the author named this +// declaration" and NOT SUFFICIENT for it, and the sufficient half cannot be recovered by +// filtering the result: the union already happened. Only the specific_names arm answers the +// question `v1.compiler.infer_lookup` level 1a asks, so `authored_import_names` is folded +// separately here rather than derived from the union. Type parameters are deliberately absent -- +// they are authored, but they are not imports, and that map answers "which declaration did an +// import name", not "what is in lexical scope". fn build_type_env(module: ResolvedModule, parent_index: Map, source_indices: Map, intern_table: InternTable, symbol_index: SymbolIndex) -> BuildTypeEnvResult { let source_indices = fold(map_keys(kernel_type_set), init: source_indices, f: (acc, name) => map_insert(acc, concat(""), build_newline_index(file: concat(""), source: name)) @@ -9568,7 +9599,7 @@ fn build_type_env(module: ResolvedModule, parent_index: Map let node_fields = inductive_fields_list_to_map(fields: compiler_inductive_fields) let kernel_recursive_types = map_keys(compiler_recursive_types) |> map(name => intern(table: intern_table, s: name).id) let kernel_recursive_type_set = fold(map_keys(compiler_recursive_types), init: empty_map(), f: (acc, name) => map_insert(acc, intern(table: intern_table, s: name).id, true)) - let kernel = TypeEnv { module_path: "", bindings: kernel_bindings, str_bindings: str_bindings_from_bindings(bindings: kernel_bindings), ancestry_str_bindings: empty_map(), parents: [], recursive_types: kernel_recursive_types, recursive_type_set: kernel_recursive_type_set, inductive_fields: node_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), symbol_index: empty_symbol_index() } + let kernel = TypeEnv { module_path: "", bindings: kernel_bindings, str_bindings: str_bindings_from_bindings(bindings: kernel_bindings), ancestry_str_bindings: empty_map(), parents: [], recursive_types: kernel_recursive_types, recursive_type_set: kernel_recursive_type_set, inductive_fields: node_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), authored_import_names: empty_map(), symbol_index: empty_symbol_index() } let module_name_str = authored_name_at(source_indices: source_indices, node: module.module) let import_parents = flat_map(module.resolved_imports, imp => @@ -9684,7 +9715,14 @@ fn build_type_env(module: ResolvedModule, parent_index: Map fold(imp.specific_names, init: acc, f: (x, n) => map_insert(x, n, true)) } ) - let unresolved_env = TypeEnv { module_path: module_name_str, bindings: all_local_bindings, str_bindings: local_str_bindings, ancestry_str_bindings: ancestry_str_bindings, parents: scope_parents, recursive_types: cycle_set, recursive_type_set: cross_type_set, inductive_fields: merged_inductive_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: source_visible_names, symbol_index: symbol_index } + let authored_import_names = fold(module.resolved_imports, init: empty_map(), f: (acc, imp) => + if imp.is_all { + acc + } else { + fold(imp.specific_names, init: acc, f: (x, n) => map_insert(x, n, true)) + } + ) + let unresolved_env = TypeEnv { module_path: module_name_str, bindings: all_local_bindings, str_bindings: local_str_bindings, ancestry_str_bindings: ancestry_str_bindings, parents: scope_parents, recursive_types: cycle_set, recursive_type_set: cross_type_set, inductive_fields: merged_inductive_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: source_visible_names, authored_import_names: authored_import_names, symbol_index: symbol_index } let resolved = resolve_env_bindings(env: unresolved_env, module_name: module_name_str, local_names: local_name_set, deps_map: all_deps_map) let resolved_env_out = resolved.env let resolved_diags = resolved.diagnostics @@ -9698,7 +9736,7 @@ fn build_type_env(module: ResolvedModule, parent_index: Map inductive_fields: resolved_env_out.inductive_fields, source_indices: resolved_env_out.source_indices, intern_table: intern_table, - source_visible_names: source_visible_names, + source_visible_names: source_visible_names, authored_import_names: authored_import_names, symbol_index: resolved_env_out.symbol_index, } let cache_str_bindings = map_merge(final_env.ancestry_str_bindings, final_env.str_bindings) @@ -9730,7 +9768,7 @@ fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map @@ -9799,7 +9837,7 @@ fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map, sourc inductive_fields: m.type_env.inductive_fields, source_indices: m.type_env.source_indices, intern_table: m.type_env.intern_table, - source_visible_names: m.type_env.source_visible_names, + source_visible_names: m.type_env.source_visible_names, authored_import_names: m.type_env.authored_import_names, symbol_index: m.type_env.symbol_index }, type_env_cache: m.type_env_cache, @@ -10908,7 +10946,7 @@ fn compiler_kernel_type_env(source_indices: Map, intern_ta let node_fields = inductive_fields_list_to_map(fields: compiler_inductive_fields) let kernel_recursive_types = map_keys(compiler_recursive_types) |> map(name => intern(table: intern_table, s: name).id) let kernel_recursive_type_set = fold(map_keys(compiler_recursive_types), init: empty_map(), f: (acc, name) => map_insert(acc, intern(table: intern_table, s: name).id, true)) - TypeEnv { module_path: "", bindings: kernel_bindings, str_bindings: str_bindings_from_bindings(bindings: kernel_bindings), ancestry_str_bindings: empty_map(), parents: [], recursive_types: kernel_recursive_types, recursive_type_set: kernel_recursive_type_set, inductive_fields: node_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), symbol_index: empty_symbol_index() } + TypeEnv { module_path: "", bindings: kernel_bindings, str_bindings: str_bindings_from_bindings(bindings: kernel_bindings), ancestry_str_bindings: empty_map(), parents: [], recursive_types: kernel_recursive_types, recursive_type_set: kernel_recursive_type_set, inductive_fields: node_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), authored_import_names: empty_map(), symbol_index: empty_symbol_index() } } fn rewire_type_env_parent_links(modules: List, source_indices: Map) -> List { @@ -10949,7 +10987,7 @@ fn rewire_type_env_parent_links(modules: List, source_indices: Map< inductive_fields: m.type_env.inductive_fields, source_indices: m.type_env.source_indices, intern_table: m.type_env.intern_table, - source_visible_names: m.type_env.source_visible_names, + source_visible_names: m.type_env.source_visible_names, authored_import_names: m.type_env.authored_import_names, symbol_index: m.type_env.symbol_index }, type_env_cache: m.type_env_cache, diff --git a/src/v1/04_lookup.dag b/src/v1/04_lookup.dag index fad222fea98..946d2f5356c 100644 --- a/src/v1/04_lookup.dag +++ b/src/v1/04_lookup.dag @@ -1,6 +1,12 @@ module v1.compiler.infer_lookup import std.induction { SubValueRelation } +import std.decl_ref { DeclarationRef, decl_ref } +import std.primitive_projection { + PrimitiveProjection, + ProjectionFidelity, HostRealizedSeam, ModeledProjection, DivergentProjection, + primitive_projection_row_for_declaration +} import v1.std.core { Node, param_node_type_expr, @@ -50,7 +56,9 @@ import v1.compiler.infer_emit_info { import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv, lookup_resolved_sig, - FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous + FuncSigLookup, FuncSigResolved, FuncSigUnresolved, FuncSigAmbiguous, + CallableCandidate, CallableIdentity, DeclaredCallable, BuiltinCallable, + parent_closure_callable_candidates, decide_callable_candidates } import v1.compiler.infer_service { @@ -80,23 +88,262 @@ fn lookup_in_scope(locals: Map, name: String) -> Node? { } } -fn lookup_func_sig(func_env: ResolvedFuncEnv, type_env: TypeEnv, name: String) -> FuncSigLookup { - if listed_import_required_bare_call_blocked(env: type_env, name: name) { - FuncSigUnresolved +// A REGISTERED BUILTIN IS A CANDIDATE, NOT A FALLBACK. +// +// This is half of gap-analysis row 30. `map_get` names two callables -- the builtin (contract at +// `std.primitives` `map_get_contract`, returning Optional) and `v2.std.collection.map_get` +// (returning Outcome>) -- and which one a bare call meant was decided by whether the +// consumer's TRANSITIVE IMPORT CLOSURE happened to reach the declaration. One unrelated import +// edge added to `extdeps.git` moved two unrelated modules' closures and silently re-typechecked +// three untouched files. Making the builtin a candidate is what lets the collision be SEEN; +// nothing here ranks it above or below a declaration, because any ranking answers the question +// silently and the ranking is not the defect -- answering at all is. +// +// THE ADMISSION TEST IS THE REGISTRY, AND NOTHING ELSE. An earlier revision of this function +// also excluded `algebra_method_template_name(name)`, reasoning that receiver-dispatched names +// (`filter`, `any`, `contains`) belong to the known-method ExprCall arm and would make every +// module declaring a collection-shaped name a collision. MEASURED, that guard was redundant for +// the names it named and fatal for the ones it did not: of the ~50 algebra template names only +// 19 carry a `builtin_function_registry` row, and `filter`/`any`/`contains`/`fold`/`map` are not +// among them -- `infer_builtin_call_type` already answers Absent for those, so they were never +// candidates and the guard changed nothing about them. What it DID exclude was the 19 names that +// do have a free-call surface, `map_get` first among them -- the exact name gap-analysis row 30 +// is about. The wall could not fire for its own specimen, and the witness that should have shown +// that returned `false` in a way indistinguishable from a broken fixture. +// +// A registry row is precisely "a bare call to this name types against a primitive", which IS the +// admission question. The method surface is a different seam with a different dispatch and needs +// no exclusion here, because a name without a registry row produces no candidate anyway. +fn builtin_callable_candidates(name: String) -> List { + match infer_builtin_call_type(name: name) { + Present { value: builtin_return } => + [CallableCandidate { + identity: BuiltinCallable { primitive_name: name }, + sig: ResolvedFuncSig { + name: name, + params: [], + inferred: builtin_return, + is_async: false, + output_provenance: [], + variant_provenance: empty_map() + }, + is_builtin: true + }] + Absent => [] + } +} + +// DID THE AUTHOR NAME THIS DECLARATION, OR DID A CLOSURE SUPPLY IT? +// +// Three states, and the third must not collapse into either other. The authority is +// `type_env.authored_import_names` -- the LISTED-import arm alone -- and NOT +// `source_visible_names`, which is a union of both import arms and cannot answer this question. +// An is_all import contributes every name in the imported module's interface plus every name +// that module acquired through ancestry, so `import v2.std.collection` would put `map_get` in +// source_visible_names without the author ever writing the word, and the ancestry half +// contributes names transitively -- which IS the closure-supply case this wall exists to refuse, +// arriving through a set whose name suggests the opposite. Membership in source_visible_names is +// necessary for "the author named this" and not sufficient for it. +type AuthorNamedVisibility + = AuthorNamedThisName + | AuthorNamedNothingForThisName + | VisibilityUnobservable + +fn author_named_visibility(type_env: TypeEnv, name: String) -> AuthorNamedVisibility { + if map_is_empty(m: type_env.authored_import_names) { + VisibilityUnobservable } else { - match lookup_resolved_sig(env: func_env, name: name) { - FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } - FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } - FuncSigUnresolved => func_sig_from_global_bare(type_env: type_env, name: name) + if map_has(m: type_env.authored_import_names, key: name) { + AuthorNamedThisName + } else { + AuthorNamedNothingForThisName } } } -fn func_sig_if_resolved(lookup: FuncSigLookup) -> ResolvedFuncSig? { - match lookup { - FuncSigResolved { sig: sig } => Present { value: sig } - FuncSigUnresolved => Absent - FuncSigAmbiguous { candidates: _ } => Absent +// IS THE BUILTIN A SECOND AUTHORITY, OR THE SAME ONE SEEN THROUGH A DECLARATION? +// +// A declared name that PROJECTS a primitive is not a rival authority for it -- it is the +// primitive's modeled surface, and collecting both would refuse a corpus that is doing exactly +// what the model says to do. `std.primitive_identity` (gunbc#8964) answers this per declaration, +// and the three answers are not interchangeable: +// +// HostRealizedSeam / ModeledProjection -- ONE authority. The declaration is the primitive's +// surface (a self-call seam, or a real body the builtin arm intercepts ahead of), so the +// builtin is dropped and the declared population decides alone. +// DivergentProjection -- TWO authorities, and the divergence is the reason. +// `v2.std.collection.map_get` returns Outcome> where the primitive returns +// Optional. Both candidates are kept and the call REFUSES. This is row 30's specimen. +// no roster row (`Absent`) -- SUPPRESS. See the arm note below: at this seam the +// two absence states are not distinguishable, and the direction is the safe one. +// +// The asymmetry is deliberate: the builtin is RETAINED only when a declared candidate positively +// says the two are different authorities. +// +// THE `Absent` ARM IS A CONSUMER-SIDE POLICY CHOICE, NOT A FACT ABOUT THE PRIMITIVE, and it is +// written down here rather than left implicit because an unexplained arm reads as an oversight. +// This seam decides nothing about the taxonomy -- it never inspects a PrimitiveIdentity, compares +// an authored symbol, or supplements a roster row; it asks one exact-declaration question and maps +// the answer onto one local question. What it DOES decide is which way THIS consumer fails when +// the roster has no row, and it fails toward today's behaviour. +// +// AND THAT ARM IS A NARROWER WALL THAN THE ONE THIS FILE ORIGINALLY CARRIED, which is stated +// rather than smoothed over. `std.primitive_identity` answers a TOTAL three-arm question that +// separates "classified as not a projection" from "nobody has classified this yet" -- and against +// that answer this seam refused the first and suppressed the second. It cannot consume that answer: +// the disposed/undisposed split is DERIVED from the five-surface census, whose interpreter roster +// reaches v2 and so cannot enter the seed's closure (`std.primitive_projection`'s own boundary +// note records this, and the 31 interpreter-only symbols are why the census cannot simply drop it). +// The seed-safe leaf answers the EXACT question only -- is there a roster row -- so both absence +// states arrive here as one `Absent`, and one direction has to cover both. +// +// SUPPRESS IS THE DIRECTION, and it is not a preference. 196 of 209 census symbols are undisposed, +// so refusing on absence would make the wall strictest exactly where it knows least and scale its +// strictness INVERSELY with how much census work remains done -- measured, that is 608 refusals +// across 79 files on four names, none of them a genuine two-authority collision. Suppressing costs +// the NoPrimitive half of the wall: a declared name unrelated to any primitive no longer rivals a +// same-named builtin. That half was never a regression guard -- it is today's behaviour, which +// already resolves such a call to the declaration -- and what survives is what row 30 is about: +// a DivergentProjection still refuses, and two declared candidates still refuse on cardinality. +// +// NEXT-RUNG TRIGGER for restoring the NoPrimitive half: a seed-safe carrier for the disposed / +// undisposed distinction. That is a census-shaped question, not a resolution-shaped one, and it +// belongs to whoever owns the surface snapshot rather than to this file. +fn declared_candidate_rivals_the_builtin(candidate: CallableCandidate) -> Bool { + match candidate.identity { + BuiltinCallable { primitive_name: _ } => false + DeclaredCallable { owner_module_path: owner, decl_name: decl } => + match primitive_projection_row_for_declaration( + declaration: decl_ref(module_path: owner, decl_name: decl) + ) { + Present { value: row } => + match row.fidelity { + HostRealizedSeam => false + ModeledProjection => false + DivergentProjection { divergence: _ } => true + } + Absent => false + } + } +} + +fn builtin_is_a_rival_authority(declared: List) -> Bool { + declared |> any(c => declared_candidate_rivals_the_builtin(candidate: c)) +} + +// COLLECT EVERY ADMISSIBLE CANDIDATE, THEN DECIDE. +// +// THE LEVELS, and they are lexical scope rather than precedence between authorities. This +// distinction is the whole difference between a correctness wall and a corpus-wide +// false-positive storm, so it is stated before the code: +// +// level 0 -- THE MODULE'S OWN DECLARATION (`func_env.local`). A module declaring a name and +// calling it is an explicit scope relation the author wrote. It wins, and it is not +// a candidate in any decision. (Body-scope binders -- let, match binders, fn and +// lambda parameters -- shadow even this, one seam up, in +// `body_shadow_aware_func_sig`.) +// level 1a - AN AUTHORED SOURCE BINDING REMOVES THE IMPLICIT BUILTIN CO-CANDIDATE, AND DOES +// NOTHING ELSE. `type_env.authored_import_names` (the specific_names arm alone -- +// an is_all import names a MODULE and belongs at 1b) carries NAME MEMBERSHIP, not +// selected declaration identity: it is a Map, so it can say "the +// author wrote this word in an import list" and cannot say WHICH declaration they +// meant. So the only thing it licenses is dropping the implicit builtin rival. +// THE REMAINING DECLARED POPULATION STILL UNDERGOES EXACT CARDINALITY ADMISSION: +// one visible name plus one declaration candidate resolves; one visible name plus +// SEVERAL still refuses. That is conservative and never silently selects the wrong +// declared function, and it is strictly weaker than "the listed import names one +// exact declaration" -- a claim this mechanism does not support and must not be +// written as though it did. Narrowing the declared set to the actually-imported +// declaration needs an exact-binding carrier (identity, not Map); +// that is NOT required to keep the explicitly-importing consumers resolving and is +// deliberately out of this cut. +// level 1b - THE TRANSITIVE CLOSURE AND THE BUILTIN SURFACE, TOGETHER. Both are visible to +// this module without the author naming either anywhere, so two answers here are +// two answers at ONE level and nothing ranks them. THIS IS THE ROW-30 INCIDENT: +// `dag/extdeps/git/object_store.dag` names `v2.std.collection` zero times; the +// declaration reached it through somebody else's edge, and one added import moved +// the closure under three untouched files. +// level 2 -- THE CORPUS-WIDE BARE-NAME CENSUS (`func_sig_from_global_bare`), consulted only +// when level 1 answered nothing at all. It is strictly outside the module's own +// visibility -- it resolves names the author never imported -- and it runs its own +// 0/1/many decision internally over the referencing module's containment chain. +// +// WHAT 1a RANKS, and why it is not the precedence this cut exists to remove: it does not rank +// CANDIDATES -- there is no first-imported-wins, no declared-beats-builtin, no import-order +// fallback. It distinguishes WHAT THE AUTHOR NAMED from WHAT A CLOSURE HAPPENED TO SUPPLY, and +// that distinction IS the defect, not a tiebreak between kinds of callable. An UNLISTED +// declaration still does not beat the builtin; that is exactly the case that refuses. +// +// AN EMPTY authored-names MAP IS A COMPATIBILITY FALLBACK, NOT AN AUTHORED-BINDING VERDICT, and +// it may never serve as a resolution authority for anything downstream: it reports that THIS +// SEAM CANNOT SEE source visibility, not that the author named nothing. A consumer needing the +// binding must take the source-resolved identity carried on the node rather than re-running +// callable lookup at a grain where source visibility has vanished. It is built at resolve time +// and is not persisted onto TypedModule.type_env, so the emit-side consumer reads an empty map +// cannot tell listed from transitive at all. An empty map therefore means THIS SEAM CANNOT +// DISTINGUISH and takes the 1a arm (no builtin co-candidate, today's behaviour) -- never +// "nothing was named", which would make the wall strictest exactly where it knows least. +// The class's rung is therefore per path and its minimum governs (DESIGN 4b): refuses on the +// resolve path, silent on the emit path. Next-rung trigger for the emit row: persisting +// authored_import_names onto TypedModule.type_env. +// +// ORDER-INDEPENDENCE: within a level the answer is a function of the candidate SET. Import +// order, parent-list order, map insertion order, and whether a module was reached through one +// path or several change which order the candidates are collected in and cannot change the +// verdict, because the verdict is taken on the population and a position is read only after the +// count has proven the population is a singleton. +fn callable_lookup_over_candidates( + func_env: ResolvedFuncEnv, + type_env: TypeEnv, + name: String +) -> FuncSigLookup { + match map_get(func_env.local, name) { + Present { value: sig } => FuncSigResolved { sig: sig } + Absent => + let declared = parent_closure_callable_candidates(env: func_env, name: name) + let builtin_admissible = match author_named_visibility(type_env: type_env, name: name) { + AuthorNamedThisName => [] + VisibilityUnobservable => [] + AuthorNamedNothingForThisName => + if builtin_is_a_rival_authority(declared: declared) { + builtin_callable_candidates(name: name) + } else { + if (declared |> count) == 0 { builtin_callable_candidates(name: name) } else { [] } + } + } + let admissible = concat(declared, builtin_admissible) + match decide_callable_candidates(candidates: admissible) { + FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } + FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } + FuncSigUnresolved => + if (admissible |> count) == 0 { + func_sig_from_global_bare(type_env: type_env, name: name) + } else { + FuncSigUnresolved + } + } + } +} + +// THE ImportScoped ARM OF `lookup_func_sig` BELOW IS THE LEGACY RESOLVER, kept byte-for-byte +// until the compatibility bankruptcy step deletes the policy fork (namespace-unique-on-chain +// operational plan, step 11). It is not the production path -- the host bracket defaults to +// namespace-only -- and widening the candidate set under it would change the very answers +// that arm exists to reproduce. + +fn lookup_func_sig(func_env: ResolvedFuncEnv, type_env: TypeEnv, name: String) -> FuncSigLookup { + if listed_import_required_bare_call_blocked(env: type_env, name: name) { + FuncSigUnresolved + } else { + if name_resolution_policy_is_namespace_only() { + callable_lookup_over_candidates(func_env: func_env, type_env: type_env, name: name) + } else { + match lookup_resolved_sig(env: func_env, name: name) { + FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } + FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } + FuncSigUnresolved => func_sig_from_global_bare(type_env: type_env, name: name) + } + } } } diff --git a/src/v1/04_method.dag b/src/v1/04_method.dag index a5cf045323a..f38113e6c70 100644 --- a/src/v1/04_method.dag +++ b/src/v1/04_method.dag @@ -58,6 +58,37 @@ data compile_dag_diagnostic_census_row_note: String = "WHY THIS ROW EXISTS, and data observe_declared_import_closure_symbol_binding_row_note: String = "CLASS B binding-source observation for declared-import-closure-only compiles (#6985). Production classify_unlisted_import_binding_source already exists in cli_run.rs; this row exposes it through the same builtin-registry pattern as compile_dag_diagnostic_census: coproduct result (observed binding vs not-runnable), registered as type_variable_node(id: declared_import_closure_binding_result) rather than a kernel record. MEASUREMENT ONLY — callers judge ListedImport vs PoolCoincidence vs refusal. DISSOLUTION: inherits compile_dag_diagnostic_census_row_note's PrimitiveDefinition identity-join trigger exactly — one additive row is linear cost against that dissolution; do not mint a fresh trigger." +// `emit_map_has` WAS A ROW OF THE REGISTRY BELOW AND HAD NO RUNTIME AT ANY TIER +// (deleted 2026-08-23; it sat between `rc_vec_ptr_eq` and `set_contains`). This note is +// module-scope because that is the only annotation grain the language models -- a block +// inside the registry body is refused, so it is attached to the declaration instead. +// +// A registry row types a bare call; an interpreter arm runs it. This name had the first and +// not the second -- no free-call arm, no method arm, no bridge -- so `emit_map_has(m:, key:)` +// typechecked clean and answered `NoSuchFunction` when evaluated. Measured by execution, not +// by reading the dispatch table: a probe calling it bare through `gunbc run` refused with +// `NoSuchFunction { name: "emit_map_has" }` while the same probe's `map_get` and `length` +// calls returned 7 and 3. +// +// WHY THAT IS THE SAME CLASS AS gap-analysis row 30 WITH THE HALVES SWAPPED, which is why it +// is deleted alongside the callable-candidate work rather than filed separately: there, a +// consumer's import closure CHANGED a name's meaning; here the closure is the only thing +// SUPPLYING one. All 20 live call sites reach the declared `v1.compiler.infer_types` +// `emit_map_has` through their own closure, so every one of them resolves to the declaration +// and none was ever served by this row -- which is exactly what made it invisible. The first +// module to call the name without that closure would have been handed a certified call with +// nothing behind it. +// +// THE COUNT: 20 CALL SITES, not 21. An earlier revision of this note said 21, which counted +// the declaration in `04_types.dag` as one of its own callers -- the same measure-the-name- +// rather-than-the-binding error this whole change exists to close, committed inside the +// receipt for it. Recorded rather than silently corrected: a count that moves between two +// tellings with no reason given is the shape people stop trusting. +// +// Deleting the row costs no call site (20 of 20 keep resolving to the declaration) and makes +// the typecheck honest: a caller outside that closure now gets an unresolved-name refusal at +// compile time instead of a runtime `NoSuchFunction`. + fn builtin_function_registry() -> Map { let m = seed_node_map(key: "count", value: int_type) let m = map_insert(m, "string_length", int_type) @@ -96,7 +127,6 @@ fn builtin_function_registry() -> Map { let m = map_insert(m, "rc_ptr_eq", bool_type) let m = map_insert(m, "rc_vec_ptr_eq", bool_type) - let m = map_insert(m, "emit_map_has", bool_type) let m = map_insert(m, "set_contains", bool_type) let m = map_insert(m, "lookup", with_optional_cardinality(n: type_variable_node(id: "map_value"))) let m = map_insert(m, "map_get", with_optional_cardinality(n: type_variable_node(id: "map_value"))) diff --git a/src/v1/04_sigs.dag b/src/v1/04_sigs.dag index 0282664ae77..b448179b950 100644 --- a/src/v1/04_sigs.dag +++ b/src/v1/04_sigs.dag @@ -13,7 +13,6 @@ import v1.std.core { import v1.compiler.infer_types { emit_map_has } import v1.compiler.infer_occurrence_binding { - ambiguity_labels_from_decide, module_path_owner_binding_decide, ModulePathBindingProjection, } @@ -73,43 +72,163 @@ type ParentSigScan { first_parent: String? } -data func_sig_lookup_outcome_note: String = "namespace-resolution-design.md 13 / 8 step 1, fn path: ResolvedFuncSig? overloaded Absent as 'keep looking' (the census fallback fires on it), so a refusal had nowhere to go — the first-hit over func_env.parents was the fn silent-pick class (fn_parent_first_hit) with NO refusal arm at all. FuncSigLookup is the 3-state outcome: FuncSigResolved binds, FuncSigUnresolved means genuinely-no-sig (census fallback may still run), FuncSigAmbiguous carries the full candidate list and REFUSES — it never falls through to a fallback. Under ImportScoped (host bracket false) the first-hit behavior remains until the downstream production flip; under NamespaceOnlyY exactly-one match across the flat parent closure resolves, two-plus refuses through module_path_owner_binding_decide. Own-module local hit stays first on both arms." +// namespace-resolution-design.md 13 / 8 step 1, fn path. `ResolvedFuncSig?` overloaded Absent as +// "keep looking" -- the census fallback fires on it -- so a refusal had nowhere to go, and the +// first-hit over func_env.parents was the fn silent-pick class (fn_parent_first_hit) with NO +// refusal arm at all. FuncSigLookup is the 3-state outcome: FuncSigResolved binds, +// FuncSigUnresolved means no admissible callable answered (the census fallback may still run), +// FuncSigAmbiguous carries every candidate and REFUSES -- it never falls through to a fallback. +// Under ImportScoped (host bracket false) the first-hit behaviour remains until the downstream +// production flip; under NamespaceOnlyY the candidates are COLLECTED and then decided. +// Own-module local hit stays first on both arms. +// +// WHAT THE 2026-08-22 CUT CHANGED, because the paragraph above was true when written and +// described a wall that could not describe the collision it was built for (gap-analysis row 30). +// Two independent mechanisms kept FuncSigAmbiguous unreachable for a cross-authority collision, +// and either one alone leaves the class open: +// +// (a) THE CANDIDATE SET WAS NEVER ASSEMBLED. `lookup_func_sig` asked this environment first +// and consulted the global/builtin path ONLY on the Unresolved arm, so a declared +// `map_get` and the registered builtin `map_get` were never co-candidates -- the declared +// one won by SHORT-CIRCUIT, and the arm was reachable only among declarations. The fix is +// here and in `v1.compiler.infer_lookup` `callable_lookup_over_candidates`: one admissible +// level is collected whole, then decided. +// (b) WHERE AMBIGUITY WAS CONSTRUCTED, ONE PROJECTION ERASED IT. `func_sig_if_resolved` mapped +// FuncSigAmbiguous to the same Absent that means "no such signature", and ten call sites +// read the seed's inference through it. That projection is deleted; see its replacement's +// note in `v1.compiler.infer_lookup`. +// +// The lesson this file keeps: a variant that exists, is named, and is documented as refusing +// still establishes nothing while nothing can construct it for the class it was built for. A +// grep for `FuncSigAmbiguous` reported this wall as built for as long as that was true. + +// WHAT A CALLABLE NAME CAN RESOLVE TO, AS AN IDENTITY RATHER THAN A SPELLING. +// +// FuncSigAmbiguous carried List and the strings were built at the point of refusal by +// pasting an owner path onto the referenced name. That is enough to print and not enough to +// decide with: it cannot represent a REGISTERED BUILTIN at all, and a builtin is one half of +// the collision this vocabulary exists to describe (gap-analysis row 30 -- `map_get` is both +// `v2.std.collection.map_get` returning Outcome> and a builtin returning +// Optional). So the candidate is the identity, and the label is a projection of it. +type CallableIdentity + = DeclaredCallable { owner_module_path: String, decl_name: String } + | BuiltinCallable { primitive_name: String } + +type CallableCandidate { + identity: CallableIdentity + sig: ResolvedFuncSig + is_builtin: Bool +} + +// The one rendering of a candidate for human-facing diagnostics. `` is spelled +// with the brackets deliberately: it is not a qualified name and must not read as one, because +// the repair for it is not "qualify it" -- there is nothing to qualify. +fn callable_identity_label(identity: CallableIdentity) -> String { + match identity { + DeclaredCallable { owner_module_path: owner, decl_name: decl } => concat(owner, ".", decl) + BuiltinCallable { primitive_name: prim } => concat("") + } +} + +fn callable_candidate_labels(candidates: List) -> List { + candidates |> map(c => callable_identity_label(identity: c.identity)) +} type FuncSigLookup = FuncSigResolved { sig: ResolvedFuncSig } | FuncSigUnresolved - | FuncSigAmbiguous { candidates: List } - -type ChainSigScan { - first_sig: ResolvedFuncSig? - owners: List + | FuncSigAmbiguous { candidates: List } + +// THE PROJECTION THAT USED TO ERASE THE AMBIGUITY ARM, SPLIT SO IT CANNOT. +// +// `func_sig_if_resolved` returned `ResolvedFuncSig?` and mapped BOTH FuncSigUnresolved and +// FuncSigAmbiguous onto Absent. Ten call sites read the seed's inference through it -- nine +// provenance/descent derivations in `v1.compiler.infer` and one argument-ordering projection in +// `v1.compiler.emit` -- so every one of them read "two authorities answered" as "no authority +// answered". Those are different states with OPPOSITE repairs (qualify the reference / declare +// the function), which is DESIGN's not-applicable-versus-malformed conflation in its third +// recorded form. +// +// WHAT THE SITES ACTUALLY DO WITH IT, because the split must not be mistaken for a behaviour +// change at a seam that has no way to refuse: every one of them derives an OPTIONAL FACT off a +// signature -- output provenance, variant provenance, a receiver's parameter name, a named-arg +// ordering -- and contributes nothing when there is no single signature to read. Contributing +// nothing remains correct for an ambiguous callee: there is no one signature whose facts could +// be read, and the reference itself is refused at the decision seams (the ExprVar and ExprCall +// arms of `v1.compiler.infer`), so no `Accepted` program reaches emit carrying one. What the +// split changes is that the carrier can no longer SPELL the collapse: a site must name the arm +// it is handling, and a future site that wants to treat ambiguity differently has the candidate +// list in hand rather than an Absent that has already forgotten it. +type NoDerivableSigReason + = DerivationSigUnresolved + | DerivationSigAmbiguous { candidates: List } + +type DerivedCalleeSig + = DerivedFromSig { sig: ResolvedFuncSig } + | NoDerivableSig { reason: NoDerivableSigReason } + +fn func_sig_for_derivation(lookup: FuncSigLookup) -> DerivedCalleeSig { + match lookup { + FuncSigResolved { sig: sig } => DerivedFromSig { sig: sig } + FuncSigUnresolved => NoDerivableSig { reason: DerivationSigUnresolved } + FuncSigAmbiguous { candidates: cands } => + NoDerivableSig { reason: DerivationSigAmbiguous { candidates: cands } } + } } fn lookup_resolved_sig_unique_across_parents(env: ResolvedFuncEnv, name: String) -> FuncSigLookup { - let scan = fold(env.parents, init: ChainSigScan { first_sig: none, owners: [] }, f: (acc, p) => + decide_callable_candidates(candidates: parent_closure_callable_candidates(env: env, name: name)) +} + + +// COLLECT, THEN DECIDE -- the whole point of this lane. +// +// EVERY member of the flat parent closure that declares the name becomes a candidate. The old +// shape stopped at the FIRST hit and kept a separate owner list only to count with; the sig it +// answered with therefore came from a position in a list rather than from a decision, and a +// second authority that was not a `fn` at all could never appear beside it. +fn parent_closure_callable_candidates(env: ResolvedFuncEnv, name: String) -> List { + fold(env.parents, init: [], f: (acc, p) => match map_get(p.local, name) { Present { value: sig } => - ChainSigScan { - first_sig: if acc.first_sig != none { acc.first_sig } else { Present { value: sig } }, - owners: concat(acc.owners, [p.name]) - } + concat(acc, [CallableCandidate { + identity: DeclaredCallable { owner_module_path: p.name, decl_name: name }, + sig: sig, + is_builtin: false + }]) Absent => acc } ) - match module_path_owner_binding_decide(owners: scan.owners) { +} + +// THE DECISION, AND IT IS ORDER-INDEPENDENT BY CONSTRUCTION. +// +// Cardinality routes through module_path_owner_binding_decide exactly as the two existing +// candidate sites do -- 0/1/many is one authority in this compiler, not a count comparison +// re-spelled per call site. Position in the list is read ONLY after the count has proven the +// population is a singleton, which is extraction rather than selection (the rule +// gunbc.roadmap_publish states for the same move). +// +// A LONE BUILTIN ANSWERS UNRESOLVED, DELIBERATELY. The builtin has no declared parameter list +// in this compiler -- `builtin_function_registry` maps a name to a RETURN TYPE -- so a +// FuncSigResolved built over it would carry `params: []` and lie about the call's shape to +// every consumer of a sig. Unresolved is what the ExprCall arm already needs in order to route +// the call to the known-builtin bridge, which is the path that types builtins correctly. So the +// builtin is a CANDIDATE in the decision and never an answer: it can make a population +// ambiguous, and on its own it hands the call back to the bridge exactly as before. +fn decide_callable_candidates(candidates: List) -> FuncSigLookup { + match module_path_owner_binding_decide( + owners: candidates |> map(c => callable_identity_label(identity: c.identity)) + ) { ModulePathBindingMiss => FuncSigUnresolved ModulePathBindingHit { owner: _ } => - match scan.first_sig { - Present { value: sig } => FuncSigResolved { sig: sig } + match candidates |> first { + Present { value: only } => + if only.is_builtin { FuncSigUnresolved } else { FuncSigResolved { sig: only.sig } } Absent => FuncSigUnresolved } ModulePathBindingAmbiguous { owners: _ } => - FuncSigAmbiguous { - candidates: ambiguity_labels_from_decide( - owners: scan.owners, - name: name, - ), - } + FuncSigAmbiguous { candidates: candidates } } } diff --git a/src/v1/05_emit.dag b/src/v1/05_emit.dag index c34cdd0cd30..293b1925cda 100644 --- a/src/v1/05_emit.dag +++ b/src/v1/05_emit.dag @@ -37,7 +37,10 @@ import std.types { is_container_type } import v1.compiler.coercion { coerce_primitive_type, coerce_container_template, target_optional_template, target_callable, can_cast, render_cast, literal_suffix, type_reference_decl_file } -import v1.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } +import v1.compiler.infer_sigs { + ResolvedFuncSig, ResolvedFuncEnv, + DerivedCalleeSig, DerivedFromSig, NoDerivableSig, func_sig_for_derivation +} import v1.compiler.infer_items { TypedModule, ResolvedGraph, ItemInfo @@ -45,7 +48,7 @@ import v1.compiler.infer_items { import v1.compiler.infer_service { UniqueAccum, OpEntry, is_typed_service_call_receiver, extract_typed_service_name } -import v1.compiler.infer_lookup { lookup_func_sig, func_sig_if_resolved } +import v1.compiler.infer_lookup { lookup_func_sig } import v1.compiler.infer { InferScope, @@ -252,7 +255,7 @@ fn emit_simple_string_interp(parts: List, target: RenderTarget, sour fn empty_emit_scope() -> InferScope { InferScope { - type_env: TypeEnv { module_path: "", bindings: empty_map(), str_bindings: empty_map(), ancestry_str_bindings: empty_map(), parents: [], recursive_types: [], recursive_type_set: empty_map(), inductive_fields: empty_map(), source_indices: empty_map(), intern_table: empty_intern_table(), source_visible_names: empty_map(), symbol_index: empty_symbol_index() }, + type_env: TypeEnv { module_path: "", bindings: empty_map(), str_bindings: empty_map(), ancestry_str_bindings: empty_map(), parents: [], recursive_types: [], recursive_type_set: empty_map(), inductive_fields: empty_map(), source_indices: empty_map(), intern_table: empty_intern_table(), source_visible_names: empty_map(), authored_import_names: empty_map(), symbol_index: empty_symbol_index() }, func_env: ResolvedFuncEnv { name: "", local: empty_map(), parents: [] }, locals: empty_map(), body_locals: empty_map(), @@ -299,8 +302,21 @@ fn lookup_item(registry: Map, name: String) -> ItemInfo? { map_get(registry, name) } +// THE ARM IS NAMED HERE RATHER THAN IN A SHARED PROJECTION THAT FORGETS IT. +// +// `order_typed_call_args` is the only consumer: it reorders NAMED actuals into declared +// parameter order and leaves the argument list untouched when there is no one signature to order +// against. An ambiguous callee has no such signature -- and cannot reach emit in an `Accepted` +// program, because the ExprCall decision seam refuses it -- so the answer is the same as the +// unresolved case and the two are written as separate arms rather than collapsed by a projection +// that returns `Absent` for both. fn lookup_func_sig_in_scope(scope: InferScope, name: String) -> ResolvedFuncSig? { - func_sig_if_resolved(lookup: lookup_func_sig(func_env: scope.func_env, type_env: scope.type_env, name: name)) + match func_sig_for_derivation( + lookup: lookup_func_sig(func_env: scope.func_env, type_env: scope.type_env, name: name) + ) { + DerivedFromSig { sig: sig } => Present { value: sig } + NoDerivableSig { reason: _ } => Absent + } } // Translate named source arguments to target positional order through infer's diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index a23865b9b13..e95796032dd 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -11905,9 +11905,31 @@ fn emit_capability_method(cap_node: Node, shared_types: Set, env: TypeEn concat(items.async_prefix, items.func_keyword, " ", emit_ident(name: authored_name(env: env, node: cap_node), target: Rust), "(", all_params, ") -> Result<", ret, ", Box>;") } +// A CALL IS A REFERENCE TO ANOTHER DECLARATION, SPELLED WITH ARGUMENTS. +// +// This predicate answers one question -- does this `data` initializer reference another +// declaration -- and `emit_data_def` uses it to choose between the JSON serializer (which can +// render only literals) and `emit_typed_expr` (which can render anything). An `ExprVar` was +// already answered `true` for exactly the right reason: a bare name is a reference. An +// `ExprCall` is the same reference with an argument list attached, and it fell through `_` to +// `false`, sending `data d: R = mk(..)` to a serializer that cannot represent a call. The +// serializer then refused with `unsupported mock expression`, so the failure surfaced one layer +// below the judgment that caused it. +// +// The match was TOTAL AT THE LEVEL EXAMINED AND BLIND ONE LEVEL DOWN: exhaustive over +// `expr_data`, no arm missing, nothing for a reviewer or a wildcard-free lens to catch -- the +// `_` carried the payload. It was wrong by this function's own definition, not incomplete. +// +// WHY NOTHING THAT WORKS TODAY MOVES: the JSON branch is guarded by record-shaped-type AND +// no-cross-refs, and every row currently on it emitted successfully, which by construction means +// its initializer holds no call. Measured at the time of the repair: 34 rows on the JSON path +// across the seed, none affected; 9 rows refused, all of them `PrimitiveIdentity` in +// `std.primitive_projection` -- the first rows in the seed closure's history to satisfy both +// conditions at once, which is why the defect stood this long unasked. fn data_value_has_cross_refs(value: Node) -> Bool { match value.expr_data { ExprVar { binding_kind: _ } => true + ExprCall { call_semantics: _, descent_evidence: _ } => true ExprListLit => value.children |> any(c => data_value_has_cross_refs(value: c)) ExprRecordLit { parent_enum: _ } => value.children |> any(f => data_value_has_cross_refs(value: field_init_node_value(n: f))) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 5a3b34b37cf..2ad3f726273 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -22,7 +22,6 @@ use crate::v1_compiler_infer_env::{ symbol_index_lookup, GlobalBareLookupState, SymbolIndex, TypeEnv, }; use crate::v1_compiler_infer_items::{item_kind, ItemInfo, ItemKind, ResolvedGraph, TypedModule}; -use crate::v1_compiler_infer_lookup::func_sig_if_resolved; use crate::v1_compiler_infer_lookup::global_bare_callable_node; use crate::v1_compiler_infer_method::infer_builtin_call_type; use crate::v1_compiler_infer_sigs::{lookup_resolved_sig, ResolvedFuncEnv, ResolvedFuncSig}; @@ -463,6 +462,23 @@ pub fn project_roadmap_acceptance_event_history_from_authority_text_builtin( pub mod roadmap_acceptance_history_carrier; +#[cfg(test)] +/// The erasing projection `func_sig_if_resolved` was deleted from production in the +/// callable-candidate cut: mapping `FuncSigAmbiguous` onto the same `None` that means "no such +/// signature" is what kept the ambiguity arm unreachable for its consumers. These tests are the +/// eleventh consumer, and they are the one place the collapse is legitimate -- they PIN the +/// legacy `ImportScoped` policy and assert its first-hit behaviour, where an ambiguity cannot +/// arise, and the other assertion is a genuine miss. Keeping the collapse here, named and local +/// to the tests, is what stops it from becoming a production projection again. +fn test_sig_or_none( + lookup: Rc, +) -> Option> { + match (*crate::v1_compiler_infer_sigs::func_sig_for_derivation(lookup)).clone() { + crate::v1_compiler_infer_sigs::DerivedCalleeSig::DerivedFromSig { sig, .. } => Some(sig), + crate::v1_compiler_infer_sigs::DerivedCalleeSig::NoDerivableSig { .. } => None, + } +} + #[cfg(test)] mod bare_reference_scanner_tests { use super::{bare_identifier_candidates, module_self_declared_names}; @@ -40903,18 +40919,15 @@ mod sigs_env_flat_parents { let b = w2_env(&format!("b{i}"), &[], vec![prev.clone()]); prev = w2_env(&format!("j{i}"), &[], vec![a, b]); } - let deep_hit = crate::v1_compiler_infer_lookup::func_sig_if_resolved( - crate::v1_compiler_infer_sigs::lookup_resolved_sig( + let deep_hit = + super::test_sig_or_none(crate::v1_compiler_infer_sigs::lookup_resolved_sig( prev.clone(), "bottom_fn".to_string(), - ), - ); - let miss = crate::v1_compiler_infer_lookup::func_sig_if_resolved( - crate::v1_compiler_infer_sigs::lookup_resolved_sig( - prev.clone(), - "absent_fn".to_string(), - ), - ); + )); + let miss = super::test_sig_or_none(crate::v1_compiler_infer_sigs::lookup_resolved_sig( + prev.clone(), + "absent_fn".to_string(), + )); let _ = tx.send(( prev.parents.len(), deep_hit.map(|s| s.inferred.name.clone()), @@ -40963,9 +40976,10 @@ mod sigs_env_flat_parents { crate::v1_rt::name_resolution_policy_set_namespace_only(false); let read = |env: &Rc, f: &str| { - crate::v1_compiler_infer_lookup::func_sig_if_resolved( - crate::v1_compiler_infer_sigs::lookup_resolved_sig(env.clone(), f.to_string()), - ) + super::test_sig_or_none(crate::v1_compiler_infer_sigs::lookup_resolved_sig( + env.clone(), + f.to_string(), + )) .map(|s| s.inferred.name.clone()) }; diff --git a/src/v1/stage0/src/emitted_population.rs b/src/v1/stage0/src/emitted_population.rs index bcc8411a7b3..30ab7114196 100644 --- a/src/v1/stage0/src/emitted_population.rs +++ b/src/v1/stage0/src/emitted_population.rs @@ -65,6 +65,7 @@ // src/std_occurrence_binding_resolve.rs // src/std_occurrence_identity.rs // src/std_pareto.rs +// src/std_primitive_projection.rs // src/std_process_termination.rs // src/std_realization_schedule.rs // src/std_reference_binding_observation.rs diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index 4eec3ac34d3..b9857af9e78 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -77,6 +77,7 @@ pub mod std_occurrence_binding_candidates; pub mod std_occurrence_binding_resolve; pub mod std_occurrence_identity; pub mod std_pareto; +pub mod std_primitive_projection; pub mod std_process_termination; pub mod std_realization_schedule; pub mod std_reference_binding_observation; diff --git a/src/v1/stage0/src/std_primitive_projection.rs b/src/v1/stage0/src/std_primitive_projection.rs new file mode 100644 index 00000000000..3ae4f79576e --- /dev/null +++ b/src/v1/stage0/src/std_primitive_projection.rs @@ -0,0 +1,281 @@ +// Generated by v1 compiler -- do not edit. +// Source module: std.primitive_projection + +use self::PrimitiveProjectionAnswer::*; +use self::ProjectionFidelity::*; +pub use crate::std_decl_ref::DeclarationRef; +pub use crate::std_decl_ref::{decl_ref, declaration_ref_eq}; +pub use crate::std_types::{List, NonEmptyStr}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn primitive_projection_seed_boundary_note() -> String { + thread_local! { + static CACHED: String = { + "The declaration-to-primitive projection is a seed-consumed substrate authority, so its import closure stops at std.decl_ref/std.types and never reaches a target roster or v2 module. This module is a strict relocation of the projection identity, fidelity, roster, and exact-declaration query formerly declared inside std.primitive_identity. std.primitive_identity consumes this authority for its census and total disposed/undisposed answer; the stage0 resolver consumes the same exact-declaration query. Required regen is the mechanical wall: src/v1 importing the former home reached gunbc.v1_interpreter_primitive_surface and then v2.std.qualified_name, which the src/v1 + dag bootstrap closure correctly refuses. The refusal is the seed-safety boundary working, not build friction.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct PrimitiveIdentity { + pub slug: NonEmptyStr, +} + +pub fn primitive_identity_slug(name: String) -> Rc { + Rc::new(PrimitiveIdentity { + slug: v1_rt::concat("primitive.".to_string(), name.clone()), + }) +} + +pub fn primitive_projection_identity_key_note() -> String { + thread_local! { + static CACHED: String = { + "The projection is keyed on std.decl_ref DeclarationRef, which is the namespace layer's own identity for a declaration (DESIGN section 3: namespace supplies declaration identity, this carrier supplies semantic primitive identity). No new identity type is minted here. The alternative shape considered and refused was a per-declaration marker row beside each fn: it is a second representation scattered across modules rather than one authority, it keys on a bare name inside a module scope so two same-named declarations collapse into one, and it has no room for a fidelity distinction. Module qualification answers the second; this single carrier answers the first; ProjectionFidelity answers the third.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn primitive_projection_fidelity_note() -> String { + thread_local! { + static CACHED: String = { + "HostRealizedSeam and ModeledProjection agree at the resolver -- both mean the declaration and the primitive are ONE authority, so a candidate collector must not report them as two. They are kept apart because they are different facts with different futures, and averaging them into one flag would erase the difference. A HostRealizedSeam body is a SELF-CALL (v2.std.decl_index decl_facts is decl_facts(pool_roots: pool_roots)): it is correct by construction, because a body that could never survive being reached is exactly what a declared host seam should look like -- reaching it recurses to the evaluation-budget refusal. A ModeledProjection body is REAL CODE that the builtin arm intercepts ahead of user-fn dispatch (v2.std.algebra length has a genuine fold_list body no bare call reaches): it is dead code that looks live, and someone will eventually edit it believing it runs. This carrier makes that observable rather than folklore. Repairing the dead-code half is NOT this carrier's job and is not claimed here.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ProjectionFidelity { + HostRealizedSeam, + ModeledProjection, + DivergentProjection { divergence: NonEmptyStr }, +} +impl ProjectionFidelity { + pub fn divergence(&self) -> NonEmptyStr { + match self { + ProjectionFidelity::HostRealizedSeam => panic!("no divergence on unit variant"), + ProjectionFidelity::ModeledProjection => panic!("no divergence on unit variant"), + ProjectionFidelity::DivergentProjection { + divergence: __val, .. + } => __val.clone(), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct PrimitiveProjection { + pub primitive: Rc, + pub declaration: Rc, + pub fidelity: Rc, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum PrimitiveProjectionAnswer { + DeclarationProjectsPrimitive { + identity: Rc, + fidelity: Rc, + }, + DeclarationProjectsNoPrimitive, + DeclarationPrimitiveUndisposed { + authored_symbol: NonEmptyStr, + }, +} + +pub fn primitive_length() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("length".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_map_insert() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("map_insert".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_map_get() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("map_get".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_empty_map() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("empty_map".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_decl_facts() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("decl_facts".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_export_signature_facts() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("export_signature_facts".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_data_decl_type_facts() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("data_decl_type_facts".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_concept_decl_facts() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("concept_decl_facts".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_concept_decl_facts_live() -> Rc { + thread_local! { + static CACHED: Rc = { + primitive_identity_slug("concept_decl_facts_live".to_string()) + }; + } + CACHED.with(|c: &Rc| c.clone()) +} + +pub fn primitive_projection_roster_note() -> String { + thread_local! { + static CACHED: String = { + "One row per declaration that is the modeled surface of a declared primitive. Every row was established by READING THE DECLARATION, not by matching its name: the HostRealizedSeam rows are declarations whose whole body is a self-call, and the ModeledProjection rows are declarations with a real body that the builtin arm intercepts ahead of. The single DivergentProjection row is v2.std.collection map_get, whose declared return type is Outcome> against the primitive's Optional; it is carried rather than omitted because omission and divergence are different answers -- omission means nobody has looked, divergence means someone looked and found two authorities. A consumer collecting call candidates must suppress the builtin co-candidate for the first two fidelities and must keep refusing on the third.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn primitive_projection_row( + primitive: Rc, + module_path: String, + decl_name: String, + fidelity: Rc, +) -> Rc { + Rc::new(PrimitiveProjection { + primitive: primitive.clone(), + declaration: decl_ref(module_path.clone(), decl_name.clone()), + fidelity: fidelity.clone(), + }) +} + +pub fn primitive_projection_roster() -> Rc>> { + Rc::new(vec![ + primitive_projection_row( + primitive_decl_facts(), + "v2.std.decl_index".to_string(), + "decl_facts".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_export_signature_facts(), + "v2.std.decl_index".to_string(), + "export_signature_facts".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_data_decl_type_facts(), + "v2.std.decl_index".to_string(), + "data_decl_type_facts".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_concept_decl_facts(), + "v2.std.concept_index".to_string(), + "concept_decl_facts".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_concept_decl_facts_live(), + "v2.std.concept_index".to_string(), + "concept_decl_facts_live".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_empty_map(), + "v2.std.collection".to_string(), + "empty_map_primitive_delegate".to_string(), + Rc::new(ProjectionFidelity::HostRealizedSeam), + ), + primitive_projection_row( + primitive_length(), + "v2.std.algebra".to_string(), + "length".to_string(), + Rc::new(ProjectionFidelity::ModeledProjection), + ), + primitive_projection_row( + primitive_map_insert(), + "v2.std.collection".to_string(), + "map_insert".to_string(), + Rc::new(ProjectionFidelity::ModeledProjection), + ), + primitive_projection_row( + primitive_empty_map(), + "v2.std.collection".to_string(), + "empty_map".to_string(), + Rc::new(ProjectionFidelity::ModeledProjection), + ), + primitive_projection_row( + primitive_map_get(), + "v2.std.collection".to_string(), + "map_get".to_string(), + Rc::new(ProjectionFidelity::DivergentProjection { + divergence: + "declared return Outcome> against the primitive's Optional" + .to_string(), + }), + ), + ]) +} + +pub fn primitive_projection_row_for_declaration( + declaration: Rc, +) -> Option> { + primitive_projection_roster().iter().cloned().fold( + None, + |acc: _, row: Rc| match acc.clone() { + Some(_) => acc.clone(), + None => { + if declaration_ref_eq(row.declaration.clone(), declaration.clone()) { + Some(row.clone()) + } else { + acc.clone() + } + } + }, + ) +} diff --git a/src/v1/stage0/src/v1_compiler_emit.rs b/src/v1/stage0/src/v1_compiler_emit.rs index d4ab2396501..d86c47218d2 100644 --- a/src/v1/stage0/src/v1_compiler_emit.rs +++ b/src/v1/stage0/src/v1_compiler_emit.rs @@ -47,12 +47,14 @@ use crate::v1_compiler_infer_env::GlobalBareLookupState::*; pub use crate::v1_compiler_infer_env::{authored_name, empty_symbol_index}; pub use crate::v1_compiler_infer_env::{GlobalBareLookupState, TypeBinding, TypeEnv}; pub use crate::v1_compiler_infer_items::{ItemInfo, ResolvedGraph, TypedModule}; -pub use crate::v1_compiler_infer_lookup::{func_sig_if_resolved, lookup_func_sig}; +pub use crate::v1_compiler_infer_lookup::lookup_func_sig; pub use crate::v1_compiler_infer_service::{ extract_typed_service_name, is_typed_service_call_receiver, }; pub use crate::v1_compiler_infer_service::{OpEntry, UniqueAccum}; -pub use crate::v1_compiler_infer_sigs::{ResolvedFuncEnv, ResolvedFuncSig}; +pub use crate::v1_compiler_infer_sigs::func_sig_for_derivation; +use crate::v1_compiler_infer_sigs::DerivedCalleeSig::{DerivedFromSig, NoDerivableSig}; +pub use crate::v1_compiler_infer_sigs::{DerivedCalleeSig, ResolvedFuncEnv, ResolvedFuncSig}; pub use crate::v1_compiler_infer_types::{ child_type_node, emit_map_has, for_each_element_type_node, is_declared_container_alias_spelling, is_product_type, is_unit_like, node_is_collection, @@ -578,6 +580,7 @@ pub fn empty_emit_scope() -> Rc { source_indices: v1_rt::rc_empty_map::>(), intern_table: empty_intern_table(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }), func_env: Rc::new(ResolvedFuncEnv { @@ -651,11 +654,16 @@ pub fn lookup_func_sig_in_scope( scope: Rc, name: String, ) -> Option> { - func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( scope.func_env.clone(), scope.type_env.clone(), name.clone(), - )) + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => Some(sig.clone()), + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => None, + } } pub fn order_typed_call_args( diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 29e29ba7b16..68a2120fe06 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -31746,6 +31746,7 @@ pub fn data_value_has_cross_refs(value: Rc) -> bool { ExprData::ExprVar { binding_kind: _, .. } => true, + ExprData::ExprCall { .. } => true, ExprData::ExprListLit => { let mut __found = false; for c in value.children.clone().iter().cloned() { diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index f3d6a951e14..35031b69098 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -100,11 +100,10 @@ use crate::v1_compiler_infer_lookup::DeclaredArgContract::{ }; pub use crate::v1_compiler_infer_lookup::{ declared_arg_types_for_method, field_summary_for_type, func_decl_binding_for_call, - func_sig_if_resolved, global_bare_callable_node, lookup_coproduct_common_field_node, - lookup_field_type_node, lookup_func_sig, lookup_in_scope, lookup_structural_method, - map_key_type_in_env, map_value_type_in_env, product_field_result_type, - resolve_known_method_node, resolve_method_receiver_type, resolve_scrutinee_type_node, - set_element_type_in_env, + global_bare_callable_node, lookup_coproduct_common_field_node, lookup_field_type_node, + lookup_func_sig, lookup_in_scope, lookup_structural_method, map_key_type_in_env, + map_value_type_in_env, product_field_result_type, resolve_known_method_node, + resolve_method_receiver_type, resolve_scrutinee_type_node, set_element_type_in_env, }; pub use crate::v1_compiler_infer_lookup::{ ConstructorDeclarationLookup, DeclarationLookupFailure, DeclaredArgContract, @@ -131,12 +130,21 @@ pub use crate::v1_compiler_infer_service::{ is_typed_service_call_receiver, service_op_entry, }; pub use crate::v1_compiler_infer_service::{OpEntry, ServiceMethodResult, UniqueAccum}; +use crate::v1_compiler_infer_sigs::CallableIdentity::{BuiltinCallable, DeclaredCallable}; +use crate::v1_compiler_infer_sigs::DerivedCalleeSig::{DerivedFromSig, NoDerivableSig}; use crate::v1_compiler_infer_sigs::FuncSigLookup::{ FuncSigAmbiguous, FuncSigResolved, FuncSigUnresolved, }; -pub use crate::v1_compiler_infer_sigs::{flatten_parent_envs, resolve_func_sigs}; +use crate::v1_compiler_infer_sigs::NoDerivableSigReason::{ + DerivationSigAmbiguous, DerivationSigUnresolved, +}; +pub use crate::v1_compiler_infer_sigs::{ + callable_candidate_labels, callable_identity_label, flatten_parent_envs, + func_sig_for_derivation, resolve_func_sigs, +}; pub use crate::v1_compiler_infer_sigs::{ - FuncSigLookup, ResolveFuncSigsResult, ResolvedFuncEnv, ResolvedFuncSig, + CallableCandidate, CallableIdentity, DerivedCalleeSig, FuncSigLookup, NoDerivableSigReason, + ResolveFuncSigsResult, ResolvedFuncEnv, ResolvedFuncSig, }; pub use crate::v1_compiler_infer_types::KernelTypeBuild; pub use crate::v1_compiler_infer_types::{ @@ -6298,7 +6306,7 @@ pub fn infer_expr_body( .. } => ambiguous_reference_refusal( name.clone(), - ambiguous_fn_candidates.clone(), + callable_candidate_labels(ambiguous_fn_candidates.clone()), span.clone(), scope.clone(), ), @@ -6649,7 +6657,7 @@ pub fn infer_expr_body( .. } => ambiguous_reference_refusal( func_name.clone(), - ambiguous_fn_candidates.clone(), + callable_candidate_labels(ambiguous_fn_candidates.clone()), span.clone(), scope.clone(), ), @@ -6661,7 +6669,14 @@ pub fn infer_expr_body( ) { Some(refusal) => refusal.clone(), None => { - let sig = func_sig_if_resolved(call_sig_lookup.clone()); + let sig = match (*call_sig_lookup.clone()).clone() { + FuncSigLookup::FuncSigResolved { + sig: resolved_call_sig, + .. + } => Some(resolved_call_sig.clone()), + FuncSigLookup::FuncSigUnresolved => None, + FuncSigLookup::FuncSigAmbiguous { candidates: _, .. } => None, + }; let sig_params = match sig.clone() { Some(s) => s.params.clone(), None => Rc::new(vec![]), @@ -11130,12 +11145,14 @@ pub fn build_per_field_for_let( ExprData::ExprCall { .. } => { let callee = expr_call_func_at(val.clone(), ctx.type_env.clone().source_indices.clone()); - match func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( ctx.func_env.clone(), ctx.type_env.clone(), callee.clone(), - )) { - Some(sig) => { + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { if ((sig.output_provenance.clone().len() as i64) > 1) { { let return_type = sig.inferred.clone(); @@ -11185,7 +11202,7 @@ pub fn build_per_field_for_let( ctx.per_field_vars.clone() } } - None => ctx.per_field_vars.clone(), + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => ctx.per_field_vars.clone(), } } ExprData::ExprMethodCall { @@ -11194,12 +11211,14 @@ pub fn build_per_field_for_let( } => { let callee = expr_method_name_at(val.clone(), ctx.type_env.clone().source_indices.clone()); - match func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( ctx.func_env.clone(), ctx.type_env.clone(), callee.clone(), - )) { - Some(sig) => { + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { if ((sig.output_provenance.clone().len() as i64) > 1) { { let return_type = sig.inferred.clone(); @@ -11249,7 +11268,7 @@ pub fn build_per_field_for_let( ctx.per_field_vars.clone() } } - None => ctx.per_field_vars.clone(), + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => ctx.per_field_vars.clone(), } } _ => ctx.per_field_vars.clone(), @@ -11547,18 +11566,24 @@ pub fn classify_let_value(val: Rc, ctx: Rc) -> Option { let callee = expr_call_func_at(val.clone(), ctx.type_env.clone().source_indices.clone()); - let from_provenance = match func_sig_if_resolved(lookup_func_sig( + let from_provenance = match (*func_sig_for_derivation(lookup_func_sig( ctx.func_env.clone(), ctx.type_env.clone(), callee.clone(), - )) { - Some(sig) => match sig.output_provenance.clone().first().cloned() { - Some(param_map) => { - classify_call_via_provenance(val.clone(), param_map.clone(), ctx.clone()) + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { + match sig.output_provenance.clone().first().cloned() { + Some(param_map) => classify_call_via_provenance( + val.clone(), + param_map.clone(), + ctx.clone(), + ), + None => None, } - None => None, - }, - None => None, + } + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => None, }; match from_provenance.clone() { Some(_) => from_provenance.clone(), @@ -12132,23 +12157,26 @@ pub fn classify_argument( arg_expr.clone(), ctx.type_env.clone().source_indices.clone(), ); - let from_provenance = match func_sig_if_resolved(lookup_func_sig( - ctx.func_env.clone(), - ctx.type_env.clone(), - callee.clone(), - )) { - Some(sig) => { - match sig.output_provenance.clone().first().cloned() { - Some(param_map) => classify_call_via_provenance( - arg_expr.clone(), - param_map.clone(), - ctx.clone(), - ), - None => None, + let from_provenance = + match (*func_sig_for_derivation(lookup_func_sig( + ctx.func_env.clone(), + ctx.type_env.clone(), + callee.clone(), + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { + match sig.output_provenance.clone().first().cloned() { + Some(param_map) => classify_call_via_provenance( + arg_expr.clone(), + param_map.clone(), + ctx.clone(), + ), + None => None, + } } - } - None => None, - }; + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => None, + }; match from_provenance.clone() { Some(prov_rel) => prov_rel.clone(), None => { @@ -12899,12 +12927,14 @@ pub fn annotate_descent(body: Rc, ctx: Rc) -> Rc { scrut.clone(), ctx.type_env.clone().source_indices.clone(), ); - match func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( ctx.func_env.clone(), ctx.type_env.clone(), callee.clone(), - )) { - Some(sig) => { + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { if ((Rc::new(v1_rt::map_keys(&sig.variant_provenance.clone())).len() as i64) > 0) @@ -12914,7 +12944,7 @@ pub fn annotate_descent(body: Rc, ctx: Rc) -> Rc { None } } - None => None, + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => None, } } _ => None, @@ -14160,27 +14190,34 @@ pub fn method_call_args_by_name( let mname = expr_method_name_at(call.clone(), source_indices.clone()); let recv = method_receiver(call.clone()); let mc_args = method_arg_nodes(call.clone()); - let base = match func_sig_if_resolved(lookup_func_sig( + let base = match (*func_sig_for_derivation(lookup_func_sig( func_env.clone(), type_env.clone(), mname.clone(), - )) { - Some(sig) => match sig.params.clone().first().cloned() { - Some(first_p) => { - let first_pname = param_node_name_at(first_p.clone(), source_indices.clone()); - if (first_pname.clone() != "".to_string()) { - v1_rt::rc_map_insert( - v1_rt::rc_empty_map::>(), - first_pname.clone(), - recv.clone(), - ) - } else { - v1_rt::rc_empty_map::>() + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { + match sig.params.clone().first().cloned() { + Some(first_p) => { + let first_pname = + param_node_name_at(first_p.clone(), source_indices.clone()); + if (first_pname.clone() != "".to_string()) { + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::>(), + first_pname.clone(), + recv.clone(), + ) + } else { + v1_rt::rc_empty_map::>() + } } + None => v1_rt::rc_empty_map::>(), } - None => v1_rt::rc_empty_map::>(), - }, - None => v1_rt::rc_empty_map::>(), + } + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => { + v1_rt::rc_empty_map::>() + } }; mc_args.iter().cloned().fold( base.clone(), @@ -14206,28 +14243,31 @@ pub fn compose_callee_provenance( func_env: Rc, let_prov: Rc>>>>, ) -> Rc>> { - match func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( func_env.clone(), type_env.clone(), callee.clone(), - )) { - Some(sig) => match sig.output_provenance.clone().first().cloned() { - Some(callee_prov) => Rc::new(v1_rt::map_keys(&callee_prov)).iter().cloned().fold( - v1_rt::rc_empty_map::>(), - |acc: Rc>>, callee_pname: String| { - match v1_rt::map_get(&callee_prov, callee_pname.clone()) { - Some(callee_rel) => { - match v1_rt::map_get(&arg_by_name, callee_pname.clone()) { - Some(arg) => { - let arg_prov = classify_body_provenance( - arg.clone(), - param_names.clone(), - param_types.clone(), - type_env.clone(), - func_env.clone(), - let_prov.clone(), - ); - Rc::new(v1_rt::map_keys(&arg_prov)).iter().cloned().fold( + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { + match sig.output_provenance.clone().first().cloned() { + Some(callee_prov) => Rc::new(v1_rt::map_keys(&callee_prov)).iter().cloned().fold( + v1_rt::rc_empty_map::>(), + |acc: Rc>>, callee_pname: String| { + match v1_rt::map_get(&callee_prov, callee_pname.clone()) { + Some(callee_rel) => { + match v1_rt::map_get(&arg_by_name, callee_pname.clone()) { + Some(arg) => { + let arg_prov = classify_body_provenance( + arg.clone(), + param_names.clone(), + param_types.clone(), + type_env.clone(), + func_env.clone(), + let_prov.clone(), + ); + Rc::new(v1_rt::map_keys(&arg_prov)).iter().cloned().fold( acc.clone(), |inner: Rc>>, pname: String| { @@ -14265,17 +14305,20 @@ pub fn compose_callee_provenance( } }, ) + } + None => acc.clone(), } - None => acc.clone(), } + None => acc.clone(), } - None => acc.clone(), - } - }, - ), - None => v1_rt::rc_empty_map::>(), - }, - None => v1_rt::rc_empty_map::>(), + }, + ), + None => v1_rt::rc_empty_map::>(), + } + } + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => { + v1_rt::rc_empty_map::>() + } } } @@ -14866,12 +14909,14 @@ pub fn collect_variant_constructors( }, ExprData::ExprCall { .. } => { let callee = expr_call_func_at(body.clone(), type_env.source_indices.clone()); - match func_sig_if_resolved(lookup_func_sig( + match (*func_sig_for_derivation(lookup_func_sig( func_env.clone(), type_env.clone(), callee.clone(), - )) { - Some(sig) => { + ))) + .clone() + { + DerivedCalleeSig::DerivedFromSig { sig: sig, .. } => { if ((authored_name_at( type_env.source_indices.clone(), sig.inferred.clone(), @@ -14914,7 +14959,7 @@ if ((Rc::new(v1_rt::map_keys(&composed_field_map)).len() as i64) > 0) { acc.clone() } } - None => acc.clone(), + DerivedCalleeSig::NoDerivableSig { reason: _, .. } => acc.clone(), } } ExprData::ExprMatch => match_arm_nodes(body.clone()).iter().cloned().fold( @@ -16779,6 +16824,7 @@ pub fn type_env_for_import(module_path: String, parent_env: Rc) -> Rc) -> source_indices: filtered.source_indices.clone(), intern_table: filtered.intern_table.clone(), source_visible_names: filtered.source_visible_names.clone(), + authored_import_names: filtered.authored_import_names.clone(), symbol_index: filtered.symbol_index.clone(), }) } @@ -16847,6 +16894,7 @@ pub fn interface_env_surface(env: Rc) -> Rc { source_indices: env.source_indices.clone(), intern_table: env.intern_table.clone(), source_visible_names: env.source_visible_names.clone(), + authored_import_names: env.authored_import_names.clone(), symbol_index: env.symbol_index.clone(), }) } @@ -17906,6 +17954,7 @@ pub fn census_fn_sig_env( source_indices: source_indices.clone(), intern_table: empty_intern_table(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: census.clone(), }); env_with_type_variable_bindings(base.clone(), tp_names.clone()) @@ -18927,6 +18976,7 @@ pub fn build_type_env( source_indices: source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }); let module_name_str = authored_name_at(source_indices.clone(), module.module.clone()); @@ -19292,6 +19342,21 @@ pub fn build_type_env( } }, ); + let authored_import_names = module.resolved_imports.clone().iter().cloned().fold( + v1_rt::rc_empty_map::(), + |acc: Rc>, imp: Rc| { + if imp.is_all.clone() { + acc.clone() + } else { + imp.specific_names.clone().iter().cloned().fold( + acc.clone(), + |x: Rc>, n: String| { + v1_rt::rc_map_insert(x, n.clone(), true) + }, + ) + } + }, + ); let unresolved_env = Rc::new(TypeEnv { module_path: module_name_str.clone(), bindings: all_local_bindings.clone(), @@ -19304,6 +19369,7 @@ pub fn build_type_env( source_indices: source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: source_visible_names.clone(), + authored_import_names: authored_import_names.clone(), symbol_index: symbol_index.clone(), }); let resolved = resolve_env_bindings( @@ -19326,6 +19392,7 @@ pub fn build_type_env( source_indices: resolved_env_out.source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: source_visible_names.clone(), + authored_import_names: authored_import_names.clone(), symbol_index: resolved_env_out.symbol_index.clone(), }); let cache_str_bindings = v1_rt::rc_map_merge( @@ -19511,6 +19578,7 @@ pub fn build_type_env_unresolved( source_indices: source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }); let module_name_str = authored_name_at(source_indices.clone(), module.module.clone()); @@ -19795,6 +19863,7 @@ pub fn build_type_env_unresolved( source_indices: source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }); let type_env_cache = Rc::new(TypeEnvCache { @@ -21040,6 +21109,7 @@ pub fn topo_resolve_types( source_indices: env.source_indices.clone(), intern_table: env.intern_table.clone(), source_visible_names: env.source_visible_names.clone(), + authored_import_names: env.authored_import_names.clone(), symbol_index: env.symbol_index.clone(), }), diagnostics: v1_rt::concat( @@ -21122,6 +21192,7 @@ pub fn topo_resolve_types( source_indices: env.source_indices.clone(), intern_table: env.intern_table.clone(), source_visible_names: env.source_visible_names.clone(), + authored_import_names: env.authored_import_names.clone(), symbol_index: env.symbol_index.clone(), }); let __tco_2 = v1_rt::concat(diagnostics, ready_accum.diagnostics.clone()); @@ -21908,6 +21979,7 @@ pub fn rewire_type_env_import_str_binding_identity( source_indices: m.type_env.clone().source_indices.clone(), intern_table: m.type_env.clone().intern_table.clone(), source_visible_names: m.type_env.clone().source_visible_names.clone(), + authored_import_names: m.type_env.clone().authored_import_names.clone(), symbol_index: m.type_env.clone().symbol_index.clone(), }), type_env_cache: m.type_env_cache.clone(), @@ -22122,6 +22194,7 @@ pub fn compiler_kernel_type_env( source_indices: source_indices.clone(), intern_table: intern_table.clone(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }) } @@ -22217,6 +22290,7 @@ pub fn rewire_type_env_parent_links( source_indices: m.type_env.clone().source_indices.clone(), intern_table: m.type_env.clone().intern_table.clone(), source_visible_names: m.type_env.clone().source_visible_names.clone(), + authored_import_names: m.type_env.clone().authored_import_names.clone(), symbol_index: m.type_env.clone().symbol_index.clone(), }), type_env_cache: m.type_env_cache.clone(), diff --git a/src/v1/stage0/src/v1_compiler_infer_env.rs b/src/v1/stage0/src/v1_compiler_infer_env.rs index 5068525538a..3971226ac13 100644 --- a/src/v1/stage0/src/v1_compiler_infer_env.rs +++ b/src/v1/stage0/src/v1_compiler_infer_env.rs @@ -56,6 +56,7 @@ pub struct TypeEnv { pub source_indices: Rc>>, pub intern_table: Rc, pub source_visible_names: Rc>, + pub authored_import_names: Rc>, pub symbol_index: Rc, pub module_path: String, } @@ -159,6 +160,7 @@ pub fn empty_type_env() -> Rc { source_indices: v1_rt::rc_empty_map::>(), intern_table: empty_intern_table(), source_visible_names: v1_rt::rc_empty_map::(), + authored_import_names: v1_rt::rc_empty_map::(), symbol_index: empty_symbol_index(), }) } @@ -1985,6 +1987,7 @@ pub fn env_with_type_variable_bindings(env: Rc, tp_names: Rc Rc>> { + match infer_builtin_call_type(name.clone()) { + Some(builtin_return) => Rc::new(vec![Rc::new(CallableCandidate { + identity: Rc::new(CallableIdentity::BuiltinCallable { + primitive_name: name.clone(), + }), + sig: Rc::new(ResolvedFuncSig { + name: name.clone(), + params: Rc::new(vec![]), + inferred: builtin_return.clone(), + is_async: false, + output_provenance: Rc::new(vec![]), + variant_provenance: v1_rt::rc_empty_map::< + String, + Rc>>>>, + >(), + }), + is_builtin: true, + })]), + None => Rc::new(vec![]), + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(tag = "_variant")] +pub enum AuthorNamedVisibility { + AuthorNamedThisName, + AuthorNamedNothingForThisName, + VisibilityUnobservable, +} + +pub fn author_named_visibility(type_env: Rc, name: String) -> AuthorNamedVisibility { + if v1_rt::map_is_empty(&type_env.authored_import_names.clone()) { + AuthorNamedVisibility::VisibilityUnobservable + } else { + if v1_rt::map_has(&type_env.authored_import_names.clone(), name.clone()) { + AuthorNamedVisibility::AuthorNamedThisName + } else { + AuthorNamedVisibility::AuthorNamedNothingForThisName + } + } +} + +pub fn declared_candidate_rivals_the_builtin(candidate: Rc) -> bool { + match (*candidate.identity.clone()).clone() { + CallableIdentity::BuiltinCallable { + primitive_name: _, .. + } => false, + CallableIdentity::DeclaredCallable { + owner_module_path: owner, + decl_name: decl, + .. + } => { + match primitive_projection_row_for_declaration(decl_ref(owner.clone(), decl.clone())) { + Some(row) => match (*row.fidelity.clone()).clone() { + ProjectionFidelity::HostRealizedSeam => false, + ProjectionFidelity::ModeledProjection => false, + ProjectionFidelity::DivergentProjection { divergence: _, .. } => true, + }, + None => false, + } + } + } +} + +pub fn builtin_is_a_rival_authority(declared: Rc>>) -> bool { + { + let mut __found = false; + for c in declared.iter().cloned() { + if declared_candidate_rivals_the_builtin(c.clone()) { + __found = true; + break; + } + } + __found + } +} + +pub fn callable_lookup_over_candidates( + func_env: Rc, + type_env: Rc, + name: String, +) -> Rc { + match v1_rt::map_get(&func_env.local.clone(), name.clone()) { + Some(sig) => Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }), + None => { + let declared = parent_closure_callable_candidates(func_env.clone(), name.clone()); + let builtin_admissible = match author_named_visibility(type_env.clone(), name.clone()) { + AuthorNamedVisibility::AuthorNamedThisName => Rc::new(vec![]), + AuthorNamedVisibility::VisibilityUnobservable => Rc::new(vec![]), + AuthorNamedVisibility::AuthorNamedNothingForThisName => { + if builtin_is_a_rival_authority(declared.clone()) { + builtin_callable_candidates(name.clone()) + } else { + if ((declared.clone().len() as i64) == 0) { + builtin_callable_candidates(name.clone()) + } else { + Rc::new(vec![]) + } + } + } + }; + let admissible = v1_rt::concat(declared.clone(), builtin_admissible.clone()); + match (*decide_callable_candidates(admissible.clone())).clone() { + FuncSigLookup::FuncSigResolved { sig: sig, .. } => { + Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }) + } + FuncSigLookup::FuncSigAmbiguous { + candidates: cands, .. + } => Rc::new(FuncSigLookup::FuncSigAmbiguous { + candidates: cands.clone(), + }), + FuncSigLookup::FuncSigUnresolved => { + if ((admissible.clone().len() as i64) == 0) { + func_sig_from_global_bare(type_env.clone(), name.clone()) + } else { + Rc::new(FuncSigLookup::FuncSigUnresolved) + } + } + } + } + } +} + pub fn lookup_func_sig( func_env: Rc, type_env: Rc, @@ -103,30 +243,26 @@ pub fn lookup_func_sig( if listed_import_required_bare_call_blocked(type_env.clone(), name.clone()) { Rc::new(FuncSigLookup::FuncSigUnresolved) } else { - match (*lookup_resolved_sig(func_env.clone(), name.clone())).clone() { - FuncSigLookup::FuncSigResolved { sig: sig, .. } => { - Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }) - } - FuncSigLookup::FuncSigAmbiguous { - candidates: cands, .. - } => Rc::new(FuncSigLookup::FuncSigAmbiguous { - candidates: cands.clone(), - }), - FuncSigLookup::FuncSigUnresolved => { - func_sig_from_global_bare(type_env.clone(), name.clone()) + if v1_rt::name_resolution_policy_is_namespace_only() { + callable_lookup_over_candidates(func_env.clone(), type_env.clone(), name.clone()) + } else { + match (*lookup_resolved_sig(func_env.clone(), name.clone())).clone() { + FuncSigLookup::FuncSigResolved { sig: sig, .. } => { + Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }) + } + FuncSigLookup::FuncSigAmbiguous { + candidates: cands, .. + } => Rc::new(FuncSigLookup::FuncSigAmbiguous { + candidates: cands.clone(), + }), + FuncSigLookup::FuncSigUnresolved => { + func_sig_from_global_bare(type_env.clone(), name.clone()) + } } } } } -pub fn func_sig_if_resolved(lookup: Rc) -> Option> { - match (*lookup.clone()).clone() { - FuncSigLookup::FuncSigResolved { sig: sig, .. } => Some(sig.clone()), - FuncSigLookup::FuncSigUnresolved => None, - FuncSigLookup::FuncSigAmbiguous { candidates: _, .. } => None, - } -} - #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct BorrowedCensusDecl { pub owner_module_path: String, @@ -1166,3 +1302,10 @@ pub fn resolve_known_method_node( } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct AuthorNamedThisName; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct AuthorNamedNothingForThisName; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct VisibilityUnobservable; diff --git a/src/v1/stage0/src/v1_compiler_infer_method.rs b/src/v1/stage0/src/v1_compiler_infer_method.rs index 904b956d1dd..70b29212781 100644 --- a/src/v1/stage0/src/v1_compiler_infer_method.rs +++ b/src/v1/stage0/src/v1_compiler_infer_method.rs @@ -210,7 +210,6 @@ pub fn builtin_function_registry() -> Rc>> { let m = v1_rt::rc_map_insert(m.clone(), "map_is_empty".to_string(), bool_type()); let m = v1_rt::rc_map_insert(m.clone(), "rc_ptr_eq".to_string(), bool_type()); let m = v1_rt::rc_map_insert(m.clone(), "rc_vec_ptr_eq".to_string(), bool_type()); - let m = v1_rt::rc_map_insert(m.clone(), "emit_map_has".to_string(), bool_type()); let m = v1_rt::rc_map_insert(m.clone(), "set_contains".to_string(), bool_type()); let m = v1_rt::rc_map_insert( m.clone(), diff --git a/src/v1/stage0/src/v1_compiler_infer_sigs.rs b/src/v1/stage0/src/v1_compiler_infer_sigs.rs index 029e28cf82f..ea9008a4d9e 100644 --- a/src/v1/stage0/src/v1_compiler_infer_sigs.rs +++ b/src/v1/stage0/src/v1_compiler_infer_sigs.rs @@ -1,28 +1,30 @@ // Generated by v1 compiler -- do not edit. // Source module: v1.compiler.infer_sigs +use self::CallableIdentity::*; +use self::DerivedCalleeSig::*; use self::FuncSigLookup::*; +use self::NoDerivableSigReason::*; pub use crate::std_induction::SubValueRelation; use crate::std_induction::SubValueRelation::*; pub use crate::std_syntax::BinOp; use crate::std_syntax::BinOp::*; +pub use crate::v1_compiler_infer_occurrence_binding::module_path_owner_binding_decide; pub use crate::v1_compiler_infer_occurrence_binding::ModulePathBindingProjection; use crate::v1_compiler_infer_occurrence_binding::ModulePathBindingProjection::*; -pub use crate::v1_compiler_infer_occurrence_binding::{ - ambiguity_labels_from_decide, module_path_owner_binding_decide, -}; pub use crate::v1_compiler_infer_types::emit_map_has; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; +use crate::v1_std_core::Cardinality::*; use crate::v1_std_core::CompilerDiagnostic::MissingAnnotation; use crate::v1_std_core::ExprData::ExprCall; use crate::v1_std_core::InferredNode::*; use crate::v1_std_core::MatchPattern::*; pub use crate::v1_std_core::{authored_name_at, expr_call_func_at, make_error_node, no_span}; +pub use crate::v1_std_core::{Cardinality, InferredNode, MatchPattern}; pub use crate::v1_std_core::{ CompilerDiagnostic, DeclaredFuncSig, ErrorNode, ExprData, NewlineIndex, Node, }; -pub use crate::v1_std_core::{InferredNode, MatchPattern}; use crate::NonEmptyBTreeSet; use crate::NonEmptyVec; use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; @@ -121,69 +123,177 @@ pub struct ParentSigScan { pub first_parent: Option, } -pub fn func_sig_lookup_outcome_note() -> String { - thread_local! { - static CACHED: String = { - "namespace-resolution-design.md 13 / 8 step 1, fn path: ResolvedFuncSig? overloaded Absent as 'keep looking' (the census fallback fires on it), so a refusal had nowhere to go — the first-hit over func_env.parents was the fn silent-pick class (fn_parent_first_hit) with NO refusal arm at all. FuncSigLookup is the 3-state outcome: FuncSigResolved binds, FuncSigUnresolved means genuinely-no-sig (census fallback may still run), FuncSigAmbiguous carries the full candidate list and REFUSES — it never falls through to a fallback. Under ImportScoped (host bracket false) the first-hit behavior remains until the downstream production flip; under NamespaceOnlyY exactly-one match across the flat parent closure resolves, two-plus refuses through module_path_owner_binding_decide. Own-module local hit stays first on both arms.".to_string() - }; +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum CallableIdentity { + DeclaredCallable { + owner_module_path: String, + decl_name: String, + }, + BuiltinCallable { + primitive_name: String, + }, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct CallableCandidate { + pub identity: Rc, + pub sig: Rc, + pub is_builtin: bool, +} + +pub fn callable_identity_label(identity: Rc) -> String { + match (*identity.clone()).clone() { + CallableIdentity::DeclaredCallable { + owner_module_path: owner, + decl_name: decl, + .. + } => v1_rt::concat(v1_rt::concat(owner.clone(), ".".to_string()), decl.clone()), + CallableIdentity::BuiltinCallable { + primitive_name: prim, + .. + } => v1_rt::concat( + v1_rt::concat("".to_string(), + ), } - CACHED.with(|c: &String| c.clone()) +} + +pub fn callable_candidate_labels(candidates: Rc>>) -> Rc> { + Rc::new({ + let mut __result = Vec::new(); + for c in candidates.iter().cloned() { + __result.push(callable_identity_label(c.identity.clone())); + } + __result + }) } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(tag = "_variant")] pub enum FuncSigLookup { - FuncSigResolved { sig: Rc }, + FuncSigResolved { + sig: Rc, + }, FuncSigUnresolved, - FuncSigAmbiguous { candidates: Rc> }, + FuncSigAmbiguous { + candidates: Rc>>, + }, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum NoDerivableSigReason { + DerivationSigUnresolved, + DerivationSigAmbiguous { + candidates: Rc>>, + }, +} +impl NoDerivableSigReason { + pub fn candidates(&self) -> Rc>> { + match self { + NoDerivableSigReason::DerivationSigUnresolved => { + panic!("no candidates on unit variant") + } + NoDerivableSigReason::DerivationSigAmbiguous { + candidates: __val, .. + } => __val.clone(), + } + } } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct ChainSigScan { - pub first_sig: Option>, - pub owners: Rc>, +#[serde(tag = "_variant")] +pub enum DerivedCalleeSig { + DerivedFromSig { sig: Rc }, + NoDerivableSig { reason: Rc }, +} + +pub fn func_sig_for_derivation(lookup: Rc) -> Rc { + match (*lookup.clone()).clone() { + FuncSigLookup::FuncSigResolved { sig: sig, .. } => { + Rc::new(DerivedCalleeSig::DerivedFromSig { sig: sig.clone() }) + } + FuncSigLookup::FuncSigUnresolved => Rc::new(DerivedCalleeSig::NoDerivableSig { + reason: Rc::new(NoDerivableSigReason::DerivationSigUnresolved), + }), + FuncSigLookup::FuncSigAmbiguous { + candidates: cands, .. + } => Rc::new(DerivedCalleeSig::NoDerivableSig { + reason: Rc::new(NoDerivableSigReason::DerivationSigAmbiguous { + candidates: cands.clone(), + }), + }), + } } pub fn lookup_resolved_sig_unique_across_parents( env: Rc, name: String, ) -> Rc { + decide_callable_candidates(parent_closure_callable_candidates( + env.clone(), + name.clone(), + )) +} + +pub fn parent_closure_callable_candidates( + env: Rc, + name: String, +) -> Rc>> { + env.parents.clone().iter().cloned().fold( + Rc::new(vec![]), + |acc: Rc>>, p: Rc| match v1_rt::map_get( + &p.local.clone(), + name.clone(), + ) { + Some(sig) => v1_rt::concat( + acc.clone(), + Rc::new(vec![Rc::new(CallableCandidate { + identity: Rc::new(CallableIdentity::DeclaredCallable { + owner_module_path: p.name.clone(), + decl_name: name.clone(), + }), + sig: sig.clone(), + is_builtin: false, + })]), + ), + None => acc.clone(), + }, + ) +} + +pub fn decide_callable_candidates(candidates: Rc>>) -> Rc { + match (*module_path_owner_binding_decide(Rc::new({ + let mut __result = Vec::new(); + for c in candidates.iter().cloned() { + __result.push(callable_identity_label(c.identity.clone())); + } + __result + }))) + .clone() { - let scan = env.parents.clone().iter().cloned().fold( - Rc::new(ChainSigScan { - first_sig: None, - owners: Rc::new(vec![]), - }), - |acc: Rc, p: Rc| match v1_rt::map_get( - &p.local.clone(), - name.clone(), - ) { - Some(sig) => Rc::new(ChainSigScan { - first_sig: if (acc.first_sig.clone() != None) { - acc.first_sig.clone() + ModulePathBindingProjection::ModulePathBindingMiss => { + Rc::new(FuncSigLookup::FuncSigUnresolved) + } + ModulePathBindingProjection::ModulePathBindingHit { owner: _, .. } => { + match candidates.clone().first().cloned() { + Some(only) => { + if only.is_builtin.clone() { + Rc::new(FuncSigLookup::FuncSigUnresolved) } else { - Some(sig.clone()) - }, - owners: v1_rt::concat(acc.owners.clone(), Rc::new(vec![p.name.clone()])), - }), - None => acc.clone(), - }, - ); - match (*module_path_owner_binding_decide(scan.owners.clone())).clone() { - ModulePathBindingProjection::ModulePathBindingMiss => { - Rc::new(FuncSigLookup::FuncSigUnresolved) - } - ModulePathBindingProjection::ModulePathBindingHit { owner: _, .. } => { - match scan.first_sig.clone() { - Some(sig) => Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }), - None => Rc::new(FuncSigLookup::FuncSigUnresolved), + Rc::new(FuncSigLookup::FuncSigResolved { + sig: only.sig.clone(), + }) + } } + None => Rc::new(FuncSigLookup::FuncSigUnresolved), } - ModulePathBindingProjection::ModulePathBindingAmbiguous { owners: _, .. } => { - Rc::new(FuncSigLookup::FuncSigAmbiguous { - candidates: ambiguity_labels_from_decide(scan.owners.clone(), name.clone()), - }) - } + } + ModulePathBindingProjection::ModulePathBindingAmbiguous { owners: _, .. } => { + Rc::new(FuncSigLookup::FuncSigAmbiguous { + candidates: candidates.clone(), + }) } } }