mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-06-01 13:56:18 +03:00
8cba6b1df8
This is f5fb720a5399ee98e45f36337b2f68a4d23a783c plus ehaas's nonnull attribute pull request currently at 4b26cb3ac610a0a070fc43e43da8b4cdf0e9101b with zig patches intact.
436 lines
14 KiB
Zig
Vendored
436 lines
14 KiB
Zig
Vendored
const std = @import("std");
|
|
const mem = std.mem;
|
|
const Allocator = mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
|
|
const Parser = @import("Parser.zig");
|
|
const StringId = @import("StringInterner.zig").StringId;
|
|
const Tree = @import("Tree.zig");
|
|
const Token = Tree.Token;
|
|
const TokenIndex = Tree.TokenIndex;
|
|
const Node = Tree.Node;
|
|
const QualType = @import("TypeStore.zig").QualType;
|
|
const Value = @import("Value.zig");
|
|
|
|
const SymbolStack = @This();
|
|
|
|
pub const Symbol = struct {
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
node: Node.OptIndex = .null,
|
|
out_of_scope: bool = false,
|
|
kind: Kind,
|
|
val: Value,
|
|
};
|
|
|
|
pub const Kind = enum {
|
|
typedef,
|
|
@"struct",
|
|
@"union",
|
|
@"enum",
|
|
decl,
|
|
def,
|
|
enumeration,
|
|
constexpr,
|
|
};
|
|
|
|
scopes: std.ArrayList(Scope) = .empty,
|
|
/// allocations from nested scopes are retained after popping; `active_len` is the number
|
|
/// of currently-active items in `scopes`.
|
|
active_len: usize = 0,
|
|
|
|
const Scope = struct {
|
|
vars: std.AutoHashMapUnmanaged(StringId, Symbol) = .empty,
|
|
tags: std.AutoHashMapUnmanaged(StringId, Symbol) = .empty,
|
|
|
|
fn deinit(self: *Scope, allocator: Allocator) void {
|
|
self.vars.deinit(allocator);
|
|
self.tags.deinit(allocator);
|
|
}
|
|
|
|
fn clearRetainingCapacity(self: *Scope) void {
|
|
self.vars.clearRetainingCapacity();
|
|
self.tags.clearRetainingCapacity();
|
|
}
|
|
};
|
|
|
|
pub fn deinit(s: *SymbolStack, gpa: Allocator) void {
|
|
std.debug.assert(s.active_len == 0); // all scopes should have been popped
|
|
for (s.scopes.items) |*scope| {
|
|
scope.deinit(gpa);
|
|
}
|
|
s.scopes.deinit(gpa);
|
|
s.* = undefined;
|
|
}
|
|
|
|
pub fn pushScope(s: *SymbolStack, p: *Parser) !void {
|
|
if (s.active_len + 1 > s.scopes.items.len) {
|
|
try s.scopes.append(p.comp.gpa, .{});
|
|
s.active_len = s.scopes.items.len;
|
|
} else {
|
|
s.scopes.items[s.active_len].clearRetainingCapacity();
|
|
s.active_len += 1;
|
|
}
|
|
}
|
|
|
|
pub fn popScope(s: *SymbolStack) void {
|
|
s.active_len -= 1;
|
|
}
|
|
|
|
pub fn findTypedef(s: *SymbolStack, p: *Parser, name: StringId, name_tok: TokenIndex, no_type_yet: bool) !?Symbol {
|
|
const prev = s.lookup(name, .vars) orelse s.lookup(name, .tags) orelse return null;
|
|
switch (prev.kind) {
|
|
.typedef => return prev,
|
|
.@"struct" => {
|
|
if (no_type_yet) return null;
|
|
try p.err(name_tok, .must_use_struct, .{p.tokSlice(name_tok)});
|
|
return prev;
|
|
},
|
|
.@"union" => {
|
|
if (no_type_yet) return null;
|
|
try p.err(name_tok, .must_use_union, .{p.tokSlice(name_tok)});
|
|
return prev;
|
|
},
|
|
.@"enum" => {
|
|
if (no_type_yet) return null;
|
|
try p.err(name_tok, .must_use_enum, .{p.tokSlice(name_tok)});
|
|
return prev;
|
|
},
|
|
else => return null,
|
|
}
|
|
}
|
|
|
|
pub fn findSymbol(s: *SymbolStack, name: StringId) ?Symbol {
|
|
return s.lookup(name, .vars);
|
|
}
|
|
|
|
pub fn findTag(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
kind: Token.Id,
|
|
name_tok: TokenIndex,
|
|
next_tok_id: Token.Id,
|
|
) !?Symbol {
|
|
// `tag Name;` should always result in a new type if in a new scope.
|
|
const prev = (if (next_tok_id == .semicolon) s.get(name, .tags) else s.lookup(name, .tags)) orelse return null;
|
|
switch (prev.kind) {
|
|
.@"enum" => if (kind == .keyword_enum) return prev,
|
|
.@"struct" => if (kind == .keyword_struct) return prev,
|
|
.@"union" => if (kind == .keyword_union) return prev,
|
|
else => unreachable,
|
|
}
|
|
if (s.get(name, .tags) == null) return null;
|
|
try p.err(name_tok, .wrong_tag, .{p.tokSlice(name_tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return null;
|
|
}
|
|
|
|
const ScopeKind = enum {
|
|
/// structs, enums, unions
|
|
tags,
|
|
/// everything else
|
|
vars,
|
|
};
|
|
|
|
/// Return the Symbol for `name` (or null if not found) in the innermost scope
|
|
pub fn get(s: *SymbolStack, name: StringId, kind: ScopeKind) ?Symbol {
|
|
return switch (kind) {
|
|
.vars => s.scopes.items[s.active_len - 1].vars.get(name),
|
|
.tags => s.scopes.items[s.active_len - 1].tags.get(name),
|
|
};
|
|
}
|
|
|
|
/// Return the Symbol for `name` (or null if not found) in the nearest active scope,
|
|
/// starting at the innermost.
|
|
fn lookup(s: *SymbolStack, name: StringId, kind: ScopeKind) ?Symbol {
|
|
var i = s.active_len;
|
|
while (i > 0) {
|
|
i -= 1;
|
|
switch (kind) {
|
|
.vars => if (s.scopes.items[i].vars.get(name)) |sym| return sym,
|
|
.tags => if (s.scopes.items[i].tags.get(name)) |sym| return sym,
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Define a symbol in the innermost scope. Does not issue diagnostics or check correctness
|
|
/// with regard to the C standard.
|
|
pub fn define(s: *SymbolStack, allocator: Allocator, symbol: Symbol) !void {
|
|
switch (symbol.kind) {
|
|
.constexpr, .def, .decl, .enumeration, .typedef => {
|
|
try s.scopes.items[s.active_len - 1].vars.put(allocator, symbol.name, symbol);
|
|
},
|
|
.@"struct", .@"union", .@"enum" => {
|
|
try s.scopes.items[s.active_len - 1].tags.put(allocator, symbol.name, symbol);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn defineTypedef(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
node: Node.Index,
|
|
) !void {
|
|
if (s.get(name, .vars)) |prev| {
|
|
switch (prev.kind) {
|
|
.typedef => {
|
|
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
|
|
if (qt.isInvalid()) return;
|
|
const non_typedef_qt = qt.type(p.comp).typedef.base;
|
|
const non_typedef_prev_qt = prev.qt.type(p.comp).typedef.base;
|
|
try p.err(tok, .redefinition_of_typedef, .{ non_typedef_qt, non_typedef_prev_qt });
|
|
if (prev.tok != 0) try p.err(prev.tok, .previous_definition, .{});
|
|
}
|
|
},
|
|
.enumeration, .decl, .def, .constexpr => {
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
try s.define(p.comp.gpa, .{
|
|
.kind = .typedef,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.node = .pack(node),
|
|
.val = .{},
|
|
});
|
|
}
|
|
|
|
pub fn defineSymbol(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
node: Node.Index,
|
|
val: Value,
|
|
constexpr: bool,
|
|
) !void {
|
|
if (s.get(name, .vars)) |prev| {
|
|
switch (prev.kind) {
|
|
.enumeration => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
.decl => {
|
|
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
} else {
|
|
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(some, node);
|
|
}
|
|
},
|
|
.def, .constexpr => if (!prev.qt.isInvalid()) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
.typedef => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
try s.define(p.comp.gpa, .{
|
|
.kind = if (constexpr) .constexpr else .def,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.node = .pack(node),
|
|
.val = val,
|
|
});
|
|
}
|
|
|
|
/// Get a pointer to the named symbol in the innermost scope.
|
|
/// Asserts that a symbol with the name exists.
|
|
pub fn getPtr(s: *SymbolStack, name: StringId, kind: ScopeKind) *Symbol {
|
|
return switch (kind) {
|
|
.tags => s.scopes.items[s.active_len - 1].tags.getPtr(name).?,
|
|
.vars => s.scopes.items[s.active_len - 1].vars.getPtr(name).?,
|
|
};
|
|
}
|
|
|
|
pub fn declareSymbol(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
node: Node.Index,
|
|
) !void {
|
|
if (s.get(name, .vars)) |prev| {
|
|
switch (prev.kind) {
|
|
.enumeration => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
.decl => {
|
|
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
} else {
|
|
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some);
|
|
}
|
|
},
|
|
.def, .constexpr => {
|
|
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
} else {
|
|
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some);
|
|
return;
|
|
}
|
|
},
|
|
.typedef => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
try s.define(p.comp.gpa, .{
|
|
.kind = .decl,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.node = .pack(node),
|
|
.val = .{},
|
|
});
|
|
|
|
// Declare out of scope symbol for functions declared in functions.
|
|
if (s.active_len > 1 and !p.comp.langopts.standard.atLeast(.c23) and qt.is(p.comp, .func)) {
|
|
try s.scopes.items[0].vars.put(p.comp.gpa, name, .{
|
|
.kind = .decl,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.node = .pack(node),
|
|
.val = .{},
|
|
.out_of_scope = true,
|
|
});
|
|
}
|
|
}
|
|
|
|
pub fn defineParam(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
node: ?Node.Index,
|
|
) !void {
|
|
if (s.get(name, .vars)) |prev| {
|
|
switch (prev.kind) {
|
|
.enumeration, .decl, .def, .constexpr => if (!prev.qt.isInvalid()) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_of_parameter, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
.typedef => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
try s.define(p.comp.gpa, .{
|
|
.kind = .def,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.node = .packOpt(node),
|
|
.val = .{},
|
|
});
|
|
}
|
|
|
|
pub fn defineTag(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
kind: Token.Id,
|
|
tok: TokenIndex,
|
|
) !?Symbol {
|
|
const prev = s.get(name, .tags) orelse return null;
|
|
switch (prev.kind) {
|
|
.@"enum" => {
|
|
if (kind == .keyword_enum) return prev;
|
|
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return null;
|
|
},
|
|
.@"struct" => {
|
|
if (kind == .keyword_struct) return prev;
|
|
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return null;
|
|
},
|
|
.@"union" => {
|
|
if (kind == .keyword_union) return prev;
|
|
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return null;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
pub fn defineEnumeration(
|
|
s: *SymbolStack,
|
|
p: *Parser,
|
|
name: StringId,
|
|
qt: QualType,
|
|
tok: TokenIndex,
|
|
val: Value,
|
|
node: Node.Index,
|
|
) !void {
|
|
if (s.get(name, .vars)) |prev| {
|
|
switch (prev.kind) {
|
|
.enumeration => if (!prev.qt.isInvalid()) {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return;
|
|
},
|
|
.decl, .def, .constexpr => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
return;
|
|
},
|
|
.typedef => {
|
|
if (qt.isInvalid()) return;
|
|
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
|
|
try p.err(prev.tok, .previous_definition, .{});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
try s.define(p.comp.gpa, .{
|
|
.kind = .enumeration,
|
|
.name = name,
|
|
.tok = tok,
|
|
.qt = qt,
|
|
.val = val,
|
|
.node = .pack(node),
|
|
});
|
|
}
|