feat(resolver): route plain bare-call author through Phase B collector via SelectedFunc [stdlib C]

Phase C of the unified resolver (R5 §C, §#3). Re-base the plain bare-name
call author onto the Phase B collector behind one shared SelectedFunc, so
every call-path consumer reads ONE author and they can no longer disagree
(fix-0102 F2). Behavior-preserving: 0722-0735 byte-identical, run_examples
stays at 475.

- SelectedFunc {decl, source, materialized?} replaces ResolvedAuthor in
  BareCallee.func; CallPlan.Target gains a `selected` arm (calls.zig).
- selectPlainCallableAuthor: resolveBareCallee's body verbatim over
  resolver.collectVisibleAuthors (.user_bare_flat) — the ONE graph-walk.
  fnDeclOfRaw mirrors imports.fnDeclOf so the collector's all-domain authors
  reproduce module_fns' fn-only view; every byte of the negative space is
  preserved (own==winner → .none; non-plain-free → .none; filter-before-count;
  ≥2 distinct → .ambiguous). No eager materialization.
- selectedFuncId materializes the FuncId on demand (shadow-only), caching into
  materialized — null until a site needs it (0102d: a shadow taken as a value
  never lowers the winner).
- Six consumers route through the one selector: lowerCall variadic packing,
  free-fn UFCS, fn-value, closure(fn), resolveCallParamTypes, and
  expandCallDefaults (decl-only, no materialization). plan() produces the
  SelectedFunc as `.selected`. Generic/comptime/foreign/builtin stay legacy.
- lower.test.zig: wire module_decls; selectPlainCallableAuthor verdicts
  (own-winner → .none; ≥2 flat → .ambiguous; own-shadow → decl+source, fid
  round-trips, materialized null).

Gate: zig build + zig build test (412 ok) + run_examples (475, byte-identical)
+ m3te ios-sim build exit 0.
This commit is contained in:
agra
2026-06-07 11:02:08 +03:00
parent f2de1a9710
commit 9568f7689f
3 changed files with 199 additions and 86 deletions

View File

@@ -72,6 +72,13 @@ pub const CallPlan = struct {
/// A callee carried by name — reflection builtin, generic / lazy fn,
/// closure / fn-pointer binding, or a not-yet-lowered namespace fn.
named: []const u8,
/// The single bare-call author `selectPlainCallableAuthor` selected for a
/// genuine flat same-name collision (R5 §#3). Carries the resolved
/// `*FnDecl` + source so `plan` and the lowering call-path read ONE
/// author and can no longer disagree (fix-0102 F2); the FuncId is
/// materialized on demand. Only set when the bare name reroutes away from
/// the first-wins winner; the common path still uses `func` / `named`.
selected: Lowering.SelectedFunc,
/// Protocol method, by index in the protocol's method table.
protocol_method: u32,
/// Foreign-class method (Obj-C / JNI), with its static-ness.
@@ -149,6 +156,28 @@ pub const CallResolver = struct {
if (std.mem.eql(u8, bare_name, "type_is_unsigned")) return refl(bare_name, .bool);
if (std.mem.eql(u8, bare_name, "type_of")) return refl(bare_name, .any);
if (std.mem.eql(u8, bare_name, "field_value")) return refl(bare_name, .any);
// Plain bare same-name flat collision (R5 §C): route through the ONE
// selector so `plan` reads the SAME author the lowering call-path
// binds — they can no longer disagree (fix-0102 F2). The gate mirrors
// `lowerCall`'s: a plain top-level identifier with no scope-mangle /
// local shadow. A generic / foreign / builtin author is not plain-free
// so the selector returns `.none`; `.ambiguous` / `.none` fall through
// to the first-wins path below, byte-for-byte.
if (std.mem.eql(u8, name, bare_name) and
(if (self.l.scope) |scope| scope.lookup(bare_name) == null else true))
{
if (self.l.current_source_file) |caller_file| {
switch (self.l.selectPlainCallableAuthor(bare_name, caller_file)) {
.func => |sf| return .{
.kind = .direct_fn,
.return_type = if (sf.decl.return_type) |rt| self.l.resolveType(rt) else .void,
.target = .{ .selected = sf },
.expands_defaults = defaultsFor(sf.decl, c.args.len),
},
.ambiguous, .none => {},
}
}
}
// Generic function — infer return type via type bindings.
if (self.l.program_index.fn_ast_map.get(name)) |fd| {
if (fd.type_params.len > 0) {