additive: compute resolver type-demanded verdicts
This commit is contained in:
@@ -461,15 +461,25 @@ fn classifyHeadKind(raw: RawDeclRef, gs: *bool, tf: *bool, pr: *bool) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// True when an author set resolves to a `foreign_class_decl` — the own author
|
||||
/// decides when present, else any flat author. Such a reference is routed to
|
||||
/// `foreign_class_refs` (its own domain) instead of the bare type/value table.
|
||||
fn authorSetIsForeignClass(set: AuthorSet) bool {
|
||||
if (set.own) |a| return std.meta.activeTag(a.raw) == .foreign_class_decl;
|
||||
for (set.flat) |a| {
|
||||
if (std.meta.activeTag(a.raw) == .foreign_class_decl) return true;
|
||||
}
|
||||
return false;
|
||||
/// True when the already-computed BARE-TYPE verdict selected a foreign-class
|
||||
/// author unambiguously. Mixed foreign/non-foreign type authors stay in
|
||||
/// `type_refs` as a type-domain ambiguity; foreign classes never preempt value or
|
||||
/// callable domains.
|
||||
fn foreignClassWinsType(set: AuthorSet, verdict: Verdict) bool {
|
||||
return switch (verdict) {
|
||||
.own_wins => if (set.own) |a| std.meta.activeTag(a.raw) == .foreign_class_decl else false,
|
||||
.single => blk: {
|
||||
var selected: ?RawAuthor = null;
|
||||
for (set.flat) |a| {
|
||||
if (!eligibleKind(.bare_type, a.raw, null)) continue;
|
||||
if (selected != null) break :blk false;
|
||||
selected = a;
|
||||
}
|
||||
const a = selected orelse break :blk false;
|
||||
break :blk std.meta.activeTag(a.raw) == .foreign_class_decl;
|
||||
},
|
||||
.ambiguous, .not_visible, .domain_filtered => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// A struct author carrying a `const_decl` member named `field` — the RAW shape
|
||||
@@ -556,13 +566,11 @@ const Domain = enum {
|
||||
/// struct/fn `type_params.len > 0` test, `structHasConstMember`).
|
||||
fn eligibleKind(domain: Domain, raw: RawDeclRef, field: ?[]const u8) bool {
|
||||
return switch (domain) {
|
||||
// Foreign classes are routed to their own domain before the type verdict, so
|
||||
// a bare TYPE author is a non-foreign named type. A type ALIAS (`Name :: <type>`,
|
||||
// a `const_decl`) is recognised by lowering via the E0 source-keyed alias cache,
|
||||
// which the resolver does not yet carry — alias authorship folds in when the
|
||||
// alias facts move into the resolver (a later S2/S4 refinement), not here.
|
||||
// Foreign classes are type authors too. Routing to `foreign_class_refs` happens
|
||||
// only after this domain verdict proves the selected type author is a foreign
|
||||
// class; mixed struct/foreign sets remain ordinary type ambiguities.
|
||||
.bare_type => switch (raw) {
|
||||
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl => true,
|
||||
.struct_decl, .enum_decl, .union_decl, .error_set_decl, .protocol_decl, .foreign_class_decl => true,
|
||||
else => false,
|
||||
},
|
||||
.value_const => raw == .const_decl,
|
||||
@@ -579,6 +587,43 @@ fn eligibleKind(domain: Domain, raw: RawDeclRef, field: ?[]const u8) bool {
|
||||
};
|
||||
}
|
||||
|
||||
fn annotationIsTypeValue(node: ?*const ast.Node) bool {
|
||||
const n = node orelse return false;
|
||||
return switch (n.data) {
|
||||
.type_expr => |te| !te.is_raw and std.mem.eql(u8, te.name, "Type"),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn callArgIsTypeDemanded(name: []const u8, index: usize) bool {
|
||||
if (index == 0 and (std.mem.eql(u8, name, "size_of") or
|
||||
std.mem.eql(u8, name, "align_of") or
|
||||
std.mem.eql(u8, name, "field_count") or
|
||||
std.mem.eql(u8, name, "type_name") or
|
||||
std.mem.eql(u8, name, "type_is_unsigned") or
|
||||
std.mem.eql(u8, name, "is_flags") or
|
||||
std.mem.eql(u8, name, "field_name") or
|
||||
std.mem.eql(u8, name, "field_value_int") or
|
||||
std.mem.eql(u8, name, "field_index")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_eq")) return index < 2;
|
||||
if (std.mem.eql(u8, name, "has_impl")) return index < 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
fn typeCategoryPatternName(name: []const u8) bool {
|
||||
const categories = [_][]const u8{
|
||||
"int", "float", "bool", "string", "void", "type", "Type",
|
||||
"struct", "enum", "union", "slice", "array", "pointer", "vector",
|
||||
};
|
||||
for (categories) |cat| {
|
||||
if (std.mem.eql(u8, name, cat)) return true;
|
||||
}
|
||||
return name.len > 0 and name[0] >= 'A' and name[0] <= 'Z';
|
||||
}
|
||||
|
||||
/// The single owning traversal. Holds the author collector + the `ResolvedProgram`
|
||||
/// it populates; threads `Ctx` (ambient source + generic scope) down the tree.
|
||||
const ResolvePass = struct {
|
||||
@@ -683,7 +728,7 @@ const ResolvePass = struct {
|
||||
} else {
|
||||
self.visit(c.callee, here);
|
||||
}
|
||||
self.visitAll(c.args, here);
|
||||
self.visitCallArgs(c, here);
|
||||
},
|
||||
.field_access => |*fa| {
|
||||
// `alias.member` whose base is a namespace import edge of the
|
||||
@@ -734,9 +779,16 @@ const ResolvePass = struct {
|
||||
if (e.else_branch) |b| self.visit(b, here);
|
||||
},
|
||||
.match_expr => |*e| {
|
||||
const type_patterns = self.matchPatternsAreTypeDemanded(e, here);
|
||||
self.visit(e.subject, here);
|
||||
for (e.arms) |arm| {
|
||||
if (arm.pattern) |pat| self.visit(pat, here);
|
||||
if (arm.pattern) |pat| {
|
||||
if (type_patterns) {
|
||||
self.visitTypeDemanded(pat, here);
|
||||
} else {
|
||||
self.visit(pat, here);
|
||||
}
|
||||
}
|
||||
self.visit(arm.body, here);
|
||||
}
|
||||
},
|
||||
@@ -746,11 +798,21 @@ const ResolvePass = struct {
|
||||
},
|
||||
.const_decl => |*cd| {
|
||||
if (cd.type_annotation) |ta| self.visit(ta, here);
|
||||
self.visit(cd.value, here);
|
||||
if (annotationIsTypeValue(cd.type_annotation)) {
|
||||
self.visitTypeDemanded(cd.value, here);
|
||||
} else {
|
||||
self.visit(cd.value, here);
|
||||
}
|
||||
},
|
||||
.var_decl => |*vd| {
|
||||
if (vd.type_annotation) |ta| self.visit(ta, here);
|
||||
if (vd.value) |v| self.visit(v, here);
|
||||
if (vd.value) |v| {
|
||||
if (annotationIsTypeValue(vd.type_annotation)) {
|
||||
self.visitTypeDemanded(v, here);
|
||||
} else {
|
||||
self.visit(v, here);
|
||||
}
|
||||
}
|
||||
},
|
||||
.assignment => |*a| {
|
||||
self.visit(a.target, here);
|
||||
@@ -919,6 +981,109 @@ const ResolvePass = struct {
|
||||
for (nodes) |n| if (n) |nn| self.visit(nn, ctx);
|
||||
}
|
||||
|
||||
fn visitCallArgs(self: *ResolvePass, c: *const ast.Call, ctx: Ctx) void {
|
||||
const cname = if (c.callee.data == .identifier) c.callee.data.identifier.name else null;
|
||||
for (c.args, 0..) |arg, i| {
|
||||
if (cname) |name| {
|
||||
if (callArgIsTypeDemanded(name, i)) {
|
||||
self.visitTypeDemanded(arg, ctx);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self.visit(arg, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn visitTypeDemanded(self: *ResolvePass, node: *const ast.Node, ctx: Ctx) void {
|
||||
const here = Ctx{
|
||||
.source = node.source_file orelse ctx.source,
|
||||
.scope = ctx.scope,
|
||||
.preseeded_decl = ctx.preseeded_decl,
|
||||
};
|
||||
switch (node.data) {
|
||||
.identifier => |id| {
|
||||
if (!id.is_raw) {
|
||||
if (lookupGeneric(here.scope, id.name)) |m| {
|
||||
self.recordTemplate(&self.out.type_refs, node, m);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.recordAuthors(.bare_type, &self.out.type_refs, node, id.name, here.source);
|
||||
},
|
||||
.type_expr => self.classifyType(node, here),
|
||||
.pack_index_type_expr => |*p| self.recordPack(&self.out.type_refs, node, p.pack_name, p.index, here.scope),
|
||||
.comptime_pack_ref => |*p| self.recordPack(&self.out.type_refs, node, p.pack_name, null, here.scope),
|
||||
.error_type_expr => |*e| {
|
||||
if (e.name) |name| self.recordAuthors(.bare_type, &self.out.type_refs, node, name, here.source);
|
||||
},
|
||||
.parameterized_type_expr => |*p| {
|
||||
self.classifyHead(node, p.name, p.is_raw, here);
|
||||
for (p.args) |arg| self.visitTypeDemanded(arg, here);
|
||||
},
|
||||
.call => |*c| {
|
||||
if (c.callee.data == .identifier) {
|
||||
const cname = c.callee.data.identifier.name;
|
||||
if (std.mem.eql(u8, cname, "type_of")) {
|
||||
self.visitAll(c.args, here);
|
||||
return;
|
||||
}
|
||||
self.classifyHead(node, cname, c.callee.data.identifier.is_raw, here);
|
||||
for (c.args) |arg| self.visitTypeDemanded(arg, here);
|
||||
} else {
|
||||
self.visit(c.callee, here);
|
||||
self.visitAll(c.args, here);
|
||||
}
|
||||
},
|
||||
.pointer_type_expr => |*p| self.visitTypeDemanded(p.pointee_type, here),
|
||||
.many_pointer_type_expr => |*p| self.visitTypeDemanded(p.element_type, here),
|
||||
.slice_type_expr => |*s| self.visitTypeDemanded(s.element_type, here),
|
||||
.optional_type_expr => |*o| self.visitTypeDemanded(o.inner_type, here),
|
||||
.array_type_expr => |*a| {
|
||||
self.visit(a.length, here);
|
||||
self.visitTypeDemanded(a.element_type, here);
|
||||
},
|
||||
.function_type_expr => |*ft| {
|
||||
for (ft.param_types) |p| self.visitTypeDemanded(p, here);
|
||||
if (ft.return_type) |rt| self.visitTypeDemanded(rt, here);
|
||||
},
|
||||
.closure_type_expr => |*ct| {
|
||||
for (ct.param_types) |p| self.visitTypeDemanded(p, here);
|
||||
if (ct.return_type) |rt| self.visitTypeDemanded(rt, here);
|
||||
},
|
||||
.tuple_type_expr => |*tt| for (tt.field_types) |ft| self.visitTypeDemanded(ft, here),
|
||||
.tuple_literal => |*tl| for (tl.elements) |el| self.visitTypeDemanded(el.value, here),
|
||||
.spread_expr => |*s| self.visitTypeDemanded(s.operand, here),
|
||||
else => self.visit(node, here),
|
||||
}
|
||||
}
|
||||
|
||||
fn matchPatternsAreTypeDemanded(self: *ResolvePass, me: *const ast.MatchExpr, ctx: Ctx) bool {
|
||||
if (self.exprDenotesTypeValue(me.subject, ctx)) return true;
|
||||
for (me.arms) |arm| {
|
||||
const pat = arm.pattern orelse continue;
|
||||
const name = switch (pat.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => continue,
|
||||
};
|
||||
if (typeCategoryPatternName(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn exprDenotesTypeValue(self: *ResolvePass, node: *const ast.Node, ctx: Ctx) bool {
|
||||
_ = self;
|
||||
return switch (node.data) {
|
||||
.identifier => |id| if (lookupGeneric(ctx.scope, id.name)) |m| !paramIsValue(m.param.*) else false,
|
||||
.type_expr,
|
||||
.pack_index_type_expr,
|
||||
.comptime_pack_ref,
|
||||
=> true,
|
||||
.call => |*c| c.callee.data == .identifier and std.mem.eql(u8, c.callee.data.identifier.name, "type_of"),
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn visitTypeParamConstraints(self: *ResolvePass, params: []const ast.StructTypeParam, ctx: Ctx) void {
|
||||
for (params) |p| self.visit(p.constraint, ctx);
|
||||
}
|
||||
@@ -950,24 +1115,26 @@ const ResolvePass = struct {
|
||||
self.recordAuthors(.value_const, &self.out.value_refs, node, id.name, ctx.source);
|
||||
}
|
||||
|
||||
/// Collect a bare name's authors AND compute its `domain` verdict. A name whose
|
||||
/// author is a `foreign_class_decl` is routed to `foreign_class_refs` (its own
|
||||
/// S2.1c domain, with the foreign-class verdict) instead of the passed
|
||||
/// type/value/callable table. Records own-wins / single / ambiguous when an
|
||||
/// Collect a bare name's authors AND compute its `domain` verdict. Foreign
|
||||
/// classes participate in the BARE-TYPE verdict like other type authors; only
|
||||
/// an unambiguously selected foreign class is routed to `foreign_class_refs`.
|
||||
/// Value/callable domains keep their requested destination and run their own
|
||||
/// eligibility filter first. Records own-wins / single / ambiguous when an
|
||||
/// eligible author is visible, and `not_visible` when the name is authored for
|
||||
/// this domain only over a namespace edge; a builtin / local / undeclared
|
||||
/// spelling (no visible author and none authored anywhere) is dropped, exactly
|
||||
/// as S2.1 dropped the empty set.
|
||||
fn recordAuthors(self: *ResolvePass, domain: Domain, table: *NodeRefTable, node: *const ast.Node, name: []const u8, from: []const u8) void {
|
||||
const set = self.res.collectVisibleAuthors(name, from, .user_bare_flat);
|
||||
const foreign = authorSetIsForeignClass(set);
|
||||
const dom: Domain = if (foreign) .foreign_class else domain;
|
||||
const dest = if (foreign) &self.out.foreign_class_refs else table;
|
||||
const verdict = self.verdictOver(dom, name, set, null);
|
||||
const verdict = self.verdictOver(domain, name, set, null);
|
||||
// Nothing visible AND not a domain author anywhere → a builtin / local /
|
||||
// undeclared spelling, never a reference of this domain — drop it (the S2.1
|
||||
// empty-set behavior). An empty set owns no `flat` slice to free.
|
||||
if (verdict == .domain_filtered and set.distinctCount() == 0) return;
|
||||
const dest = if (domain == .bare_type and foreignClassWinsType(set, verdict))
|
||||
&self.out.foreign_class_refs
|
||||
else
|
||||
table;
|
||||
self.replaceRef(dest, node, .{ .authors = .{ .set = set, .verdict = verdict } });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user