Decl
has two main responsibilities today.
The first is to act as the "subject" of semantic analysis for anything analyzed in a comptime
context. For instance, when analyzing the value of a container-level const
, that declaration's Decl
is the "owner" of that Sema
; errors are marked on it, source locations resolved relative to it, etc. This is also where type owner decls come from - we need some context in which to perform type resolution, so we need a Decl
associated with the type itself.
The second is to represent a globally named and/or addressable value. For instance, container-level const
s are named and addressable. More interestingly, so are generic instantiations - you can't take their address in Zig today, but they sure as hell have one, and same with name. This is where function instance owner decls come from.
Interestingly, above, we found that owner decls for types and owner decls for function instances are fulfiling two totally separate purposes! To me, that's a pretty clear indication that those purposes should be separated.
Split Decl
into two types: Csu
and Decl
.
Csu
stands for "Comptime Sema Unit" (thanks InK for pointing me towards this nomenclature!). It is the subject of all comptime semantic analysis (i.e. all analysis which is not of a runtime function body). It appears as follows.
pub const Csu = struct {
/// What is being analyzed?
source: Source,
analysis: enum {
not_analyzed,
in_progress,
file_failure,
dependency_failure,
sema_failure,
complete,
// Note the lack of `codegen_failure` - `Csu` doesn't care about your codegen!
},
/// The file that error messages from this `Csu` should appear within.
src_file: *File,
/// The source location that error messages from this `Csu` are relative to.
src_node: Ast.Node.Index,
pub const Source = union(enum) {
/// This `Csu` corresponds to a `usingnamespace` declaration.
@"usingnamespace": TrackedInst.Index,
/// This `Csu` corresponds to a `comptime` declaration.
@"comptime": TrackedInst.Index,
/// This `Csu` corresponds to a `test` declaration.
@"test": TrackedInst.Index,
/// This `Csu` corresponds to a non-test named declaration.
decl: TrackedInst.Index,
/// This `Csu` is the context for resolution of this type.
type: InternPool.Index,
};
// [...declarations...]
};
The state here is, as you can see, pretty minimal. It's just "what are we analyzing" (source
), "what's the state of that analysis" (analysis
), and "where should compile errors point" (src_file
+src_node
).
The new Decl
, then, is a subset of the old one. It represents a globally named addressable value (if anyone can think of a better name than Decl
, I'm all ears!). It looks like this:
pub const Decl = struct {
name: InternPool.NullTerminatedString,
/// Is `name` already fully qualified?
name_fully_qualified: bool = false,
/// The namespace which this `Decl` was created within.
/// If this is the root `Decl` of a file, this is the namespace of the file's root struct type itself.
src_namespace: Namespace.Index,
/// Absolute line number corresponding to the source location of this declaration.
/// Used by backends to generate debug info.
src_line: u32,
/// Set if this `Decl` corresponds to a source declaration marked `export`.
/// If so, it will be exported from the `Zcu` under `name` once it `has_tv`.
is_exported: bool,
/// Whether `ty` through `addrspace` are populated.
has_tv: bool,
// Once we fix up comptime-mutable memory these two will just be `val: InternPool.Index`.
ty: Type,
val: Value,
@"linksection": InternPool.OptionalNullTerminatedString,
alignment: Alignment,
@"addrspace": std.builtin.AddressSpace,
/// If this `Decl` undergoes analysis, it's in this `Csu`.
/// If this is `.none`, then `has_tv` must be always set, e.g. perhaps this is a generic function instance.
csu: Csu.OptionalIndex,
};
Note that is_pub
is gone. This state is moved into Namespace
, which will be as follows:
pub const Namespace = struct {
parent: OptionalIndex,
file_scope: *File,
/// Will be a struct, enum, union or opaque.
ty: InternPool.Index,
/// All names in this namespace which are marked `pub`.
pub_decls: std.ArrayHashMapUnmanaged(Decl.Index, void, DeclContext, true) = .{},
/// All names in this namespace which are *not* marked `pub`.
priv_decls: std.ArrayHashMapUnmanaged(Decl.Index, void, DeclContext, true) = .{},
/// All `usingnamespace` declarations in this namespace which are marked `pub`.
pub_usingnamespaces: std.ArrayListUnmanaged(Csu.Index) = .{},
/// All `usingnamespace` declarations in this namespace which are *not* marked `pub`.
priv_usingnamespaces: std.ArrayListUnmanaged(Csu.Index) = .{},
/// Other things within this namespace - `comptime` and `test` declarations.
/// These are basically just here for incremental compilation reasons.
other_members: std.ArrayListUnmanaged(Csu.Index) = .{},
// [...declarations...]
};
Q: How do namespace types work?
A: They have a Csu
, but not a Decl
. The type's name is stored straight in the InternPool
, since this is a property of the type itself.
Q: How do generic function instances work?
A: They have a Decl
, but not a Csu
. The instance is named and has an address, but no comptime semantic analysis is necessary within the function instance itself.
Q: How does reference_table
work?
A: References map between any Sema units (currently named Depender
, to be renamed to SemaUnit
; either a runtime function or a Csu
). Note that for reasons related to incremental compilation I'm planning to rework how the reference table works a little anyway.
Q: How does tracking of Decl
and Csu
across incremental updates work?
A: In Zcu.scanNamespace
, we try to match source declarations up with the old namespace members - that's why other_members
exists! We do it for everything based on the TrackedInst.Index
, which is in the Csu
of the namespace's declarations.
Okay, I've settled down the names and structures a bit more, so will paste them below. Note that I've given efficient DOD representations of the types as e.g.
Cau.Repr
- these are trivially serialized and memory-efficient. There should be no padding, so we can store them in plainArrayList
s in theInternPool
, and have accessor methods (akin toInternPool.loadStructType
etc) to load the more elegant representation. You might notice thatsrc_namespace
is in bothCau
andNav
- this is necessary because they're relevant to both (see doc comments), but for memory efficiency,Nav.Repr
doesn't store it unnecessarily.