fix(stdlib/E4): type-fn head gate selects the TYPE-FUNCTION author (ordinary fn must not vouch)
attempt-7 made the type-fn head gate kind-aware (a non-function no longer
vouches), but it still accepted ANY function author: a directly-visible
ORDINARY function (`Make :: () -> s32`, zero `$`-params) authorized a hidden
2-flat-hop type-function head (`Make :: ($T) -> Type`), so `size_of(Make(s64))`
silently instantiated the 2-hop type-fn and printed `size=8` at exit 0.
Narrow the author view from "any fn_decl" to "a TYPE-FUNCTION" via a new
`typeFnAuthor` predicate (`fnDeclOfRaw` + `type_params.len > 0`), the same
discriminator every instantiation site uses to recognize a type-fn head. Both
`flatFnAuthorVisible` and `flatFnAuthorAmbiguous` now count only type-fn
authors, so a same-name ordinary function — which cannot be the type head being
instantiated — does not vouch for a 2-hop type-fn head.
Regression 0771: main -> b (`Make :: () -> s32` ordinary fn + flat-imports c)
-> c (`Make :: ($T) -> Type`); `size_of(Make(s64))` -> "type 'Make' is not
visible", exit 1 (fail-before on 94c3cd7: size=8 exit 0). 0770 (non-fn vouch),
0769 (type-fn ambiguity), 0768/0767/0766-0763, 0208/0210 (valid type-fn heads),
0544/0706/0105 and FFI all green & byte-identical.
This commit is contained in:
25
examples/0771-modules-type-fn-head-ordinary-fn-no-vouch.sx
Normal file
25
examples/0771-modules-type-fn-head-ordinary-fn-no-vouch.sx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// A type-returning FUNCTION head (`Make(s64)` where `Make :: ($T) -> Type`) is
|
||||||
|
// NON-transitive even when a DIRECT flat import authors the same name as an
|
||||||
|
// ORDINARY (non-type-returning) function. `main` flat-imports `b.sx`; `b.sx`
|
||||||
|
// declares `Make :: () -> s32` (a plain function, NOT a type-fn) AND flat-imports
|
||||||
|
// `c.sx`, whose `Make` IS the type-returning function. The only TYPE-FN author of
|
||||||
|
// `Make` is two flat hops away (main → b → c), so the bare `Make(s64)` head must
|
||||||
|
// emit the "type 'Make' is not visible" diagnostic and poison — the visible 1-hop
|
||||||
|
// ordinary `Make :: () -> s32` must NOT vouch for it.
|
||||||
|
//
|
||||||
|
// Regression (Phase E4 attempt-8, finding E4-type-fn-head-hidden-by-visible-
|
||||||
|
// nontypefn): attempt-7's `headFnLeak` decided visibility from any FUNCTION
|
||||||
|
// author (`fnDeclOfRaw != null`), so the visible ordinary `Make` function (which
|
||||||
|
// CANNOT be the type head being instantiated) still vouched — the global
|
||||||
|
// `fn_ast_map` type-fn silently instantiated and `size_of(Make(s64))` printed 8
|
||||||
|
// at exit 0 instead of the visibility diagnostic. The fix narrows the author view
|
||||||
|
// to TYPE-FUNCTIONS (`typeFnAuthor`: a `fn_decl` with ≥1 `$`-param), the same
|
||||||
|
// discriminator every instantiation site uses to recognize a type-fn head.
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "0771-modules-type-fn-head-ordinary-fn-no-vouch/b.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
print("size={}\n", size_of(Make(s64)));
|
||||||
|
0
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// The directly-imported (1-hop) author of the NAME `Make` — but as an ORDINARY
|
||||||
|
// function (`() -> s32`), NOT a type-returning function. It flat-imports c.sx
|
||||||
|
// (where the real `Make` type-fn lives, two hops from a file that imports b.sx).
|
||||||
|
// A same-name ordinary function must not vouch for the 2-hop type-fn head: it has
|
||||||
|
// zero `$`-params, so it cannot be the type head `Make(s64)` is instantiating.
|
||||||
|
#import "c.sx";
|
||||||
|
Make :: () -> s32 { return 7; }
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// The real type-returning function `Make`, two flat hops from a file that
|
||||||
|
// imports b.sx. A file importing only b.sx must NOT see this head.
|
||||||
|
Make :: ($T: Type) -> Type {
|
||||||
|
return struct { x: T; };
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: type 'Make' is not visible; #import the module that declares it
|
||||||
|
--> examples/0771-modules-type-fn-head-ordinary-fn-no-vouch.sx:23:32
|
||||||
|
|
|
||||||
|
23 | print("size={}\n", size_of(Make(s64)));
|
||||||
|
| ^^^^
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -2329,6 +2329,19 @@ pub const Lowering = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TRUE iff `ref` is a TYPE-FUNCTION head author — a `fn_decl` (or const-
|
||||||
|
/// wrapped fn) declaring at least one `$`-parameter, i.e. instantiable as a
|
||||||
|
/// bare type head (`Make(s64)` where `Make :: ($T) -> Type`). Mirrors the
|
||||||
|
/// `fd.type_params.len > 0` gate every instantiation site uses to recognize a
|
||||||
|
/// type-fn head, so an ORDINARY same-name function (`Make :: () -> s32`, zero
|
||||||
|
/// type params) is NOT a type-fn author and does NOT vouch for a hidden 2-flat-
|
||||||
|
/// hop type-fn head (E4 attempt-8: a `fn_decl != null` author view let any
|
||||||
|
/// visible function — type-fn or not — authorize a type head).
|
||||||
|
fn typeFnAuthor(ref: resolver_mod.RawDeclRef) bool {
|
||||||
|
const fd = fnDeclOfRaw(ref) orelse return false;
|
||||||
|
return fd.type_params.len > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Materialize (lower-on-demand) the FuncId for a selected bare-call author,
|
/// Materialize (lower-on-demand) the FuncId for a selected bare-call author,
|
||||||
/// caching into `sf.materialized`. Shadow-only: the winner owns the
|
/// caching into `sf.materialized`. Shadow-only: the winner owns the
|
||||||
/// name-keyed slot and lowers through the lazy path, so
|
/// name-keyed slot and lowers through the lazy path, so
|
||||||
@@ -14063,8 +14076,9 @@ pub const Lowering = struct {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// KIND-AWARE: visible iff a directly-reachable (own or 1-hop flat) author
|
// KIND-AWARE: visible iff a directly-reachable (own or 1-hop flat) author
|
||||||
// is a FUNCTION. A same-name 1-hop non-function does NOT vouch for a
|
// is itself a TYPE-FUNCTION. A same-name 1-hop non-function (attempt-7) OR
|
||||||
// type-fn head whose real author is 2 flat hops away (E4 attempt-7).
|
// ordinary non-type function (attempt-8) does NOT vouch for a type-fn head
|
||||||
|
// whose real author is 2 flat hops away.
|
||||||
if (self.flatFnAuthorVisible(name, from)) return false;
|
if (self.flatFnAuthorVisible(name, from)) return false;
|
||||||
if (self.diagnostics) |d|
|
if (self.diagnostics) |d|
|
||||||
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
d.addFmt(.err, span, "type '{s}' is not visible; #import the module that declares it", .{name});
|
||||||
@@ -14072,8 +14086,9 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// TRUE iff bare `name` has ≥2 DISTINCT direct flat-import authors that are
|
/// TRUE iff bare `name` has ≥2 DISTINCT direct flat-import authors that are
|
||||||
/// type-returning FUNCTIONS (`fn_decl`s — `fnDeclOfRaw` unwraps a const-wrapped
|
/// TYPE-FUNCTIONS (`typeFnAuthor`: a `fn_decl` with ≥1 `$`-param — an ordinary
|
||||||
/// fn) and the querying source authors NONE itself. The querying source's OWN
|
/// same-name function does not count) and the querying source authors NONE
|
||||||
|
/// itself. The querying source's OWN
|
||||||
/// author wins outright (own-wins), so an own author short-circuits to "not
|
/// author wins outright (own-wins), so an own author short-circuits to "not
|
||||||
/// ambiguous" — the existing single-author path instantiates it. Diamond
|
/// ambiguous" — the existing single-author path instantiates it. Diamond
|
||||||
/// imports of the SAME author collapse in `collectVisibleAuthors`'s
|
/// imports of the SAME author collapse in `collectVisibleAuthors`'s
|
||||||
@@ -14087,29 +14102,31 @@ pub const Lowering = struct {
|
|||||||
if (set.own != null) return false; // own-wins
|
if (set.own != null) return false; // own-wins
|
||||||
var fn_authors: usize = 0;
|
var fn_authors: usize = 0;
|
||||||
for (set.flat) |fa| {
|
for (set.flat) |fa| {
|
||||||
if (fnDeclOfRaw(fa.raw) != null) fn_authors += 1;
|
if (typeFnAuthor(fa.raw)) fn_authors += 1;
|
||||||
}
|
}
|
||||||
return fn_authors >= 2;
|
return fn_authors >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TRUE iff bare `name` has at least one DIRECTLY-visible author — the
|
/// TRUE iff bare `name` has at least one DIRECTLY-visible author — the
|
||||||
/// querying source's OWN author or a 1-hop flat-import author — that is a
|
/// querying source's OWN author or a 1-hop flat-import author — that is a
|
||||||
/// FUNCTION decl (`fnDeclOfRaw` unwraps a const-wrapped fn). The KIND-AWARE
|
/// TYPE-FUNCTION (`typeFnAuthor`: a `fn_decl` with ≥1 `$`-param). The KIND-AWARE
|
||||||
/// analogue of `isNameVisible` for a type-fn head: a same-name 1-hop
|
/// analogue of `isNameVisible` for a type-fn head: a same-name 1-hop
|
||||||
/// NON-function (a value const `Make :: 123`, a named type) is NOT a function
|
/// NON-function (a value const `Make :: 123`, a named type) does NOT vouch
|
||||||
/// author and does NOT vouch, so a type-fn whose only directly-visible
|
/// (attempt-7), and — crucially — neither does a same-name 1-hop ORDINARY
|
||||||
/// same-name author is a non-fn — its real author 2 flat hops away — is
|
/// function (`Make :: () -> s32`, zero `$`-params), which cannot be the type
|
||||||
/// correctly invisible. Mirrors `flatFnAuthorAmbiguous`'s fn-only author view
|
/// head being instantiated (attempt-8). So a type-fn whose only directly-
|
||||||
/// (E4 attempt-7: closes the `isNameVisible` name-only short-circuit leak).
|
/// visible same-name author is a non-fn OR a non-type-fn — its real author 2
|
||||||
|
/// flat hops away — is correctly invisible. Mirrors `flatFnAuthorAmbiguous`'s
|
||||||
|
/// type-fn-only author view.
|
||||||
fn flatFnAuthorVisible(self: *Lowering, name: []const u8, from: []const u8) bool {
|
fn flatFnAuthorVisible(self: *Lowering, name: []const u8, from: []const u8) bool {
|
||||||
var res = self.resolver();
|
var res = self.resolver();
|
||||||
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
|
const set = res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||||
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
defer if (set.flat.len > 0) self.alloc.free(set.flat);
|
||||||
if (set.own) |own| {
|
if (set.own) |own| {
|
||||||
if (fnDeclOfRaw(own.raw) != null) return true;
|
if (typeFnAuthor(own.raw)) return true;
|
||||||
}
|
}
|
||||||
for (set.flat) |fa| {
|
for (set.flat) |fa| {
|
||||||
if (fnDeclOfRaw(fa.raw) != null) return true;
|
if (typeFnAuthor(fa.raw)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user