add ArrayList alias to std
This commit is contained in:
@@ -13,6 +13,8 @@ roadmap and milestone history.
|
|||||||
- package-level functions, globals, native type declarations, and `Name :: alias T`
|
- package-level functions, globals, native type declarations, and `Name :: alias T`
|
||||||
- directory packages with merged declarations
|
- directory packages with merged declarations
|
||||||
- file-local relative imports, import aliases, and qualified member access
|
- file-local relative imports, import aliases, and qualified member access
|
||||||
|
- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories,
|
||||||
|
named types, and globals retain their original declaration or storage identity
|
||||||
- native top-level declarations beginning with `_` are visible only within their source file; locals, fields, parameters, and C declarations are unaffected
|
- native top-level declarations beginning with `_` are visible only within their source file; locals, fields, parameters, and C declarations are unaffected
|
||||||
- relative `.h` imports as synthetic C header package namespaces
|
- relative `.h` imports as synthetic C header package namespaces
|
||||||
- root `main` validation with trap executable recovery for missing or unusable entry points
|
- root `main` validation with trap executable recovery for missing or unusable entry points
|
||||||
@@ -121,6 +123,7 @@ The six spellings are reserved only as direct unqualified calls. A qualified cal
|
|||||||
|
|
||||||
### standard packages
|
### standard packages
|
||||||
|
|
||||||
|
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
|
||||||
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
|
||||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||||
- `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, one-shot `read`/`write`, and allocation-free `write_all`
|
- `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, one-shot `read`/`write`, and allocation-free `write_all`
|
||||||
|
|||||||
@@ -814,7 +814,14 @@
|
|||||||
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
|
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
|
||||||
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing
|
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing
|
||||||
|
|
||||||
34. re-exports so `std` can re-export e.g. `ArrayList(T)`, so users can do `std.ArrayList(i32)` instead of `std.arraylist.ArrayList(i32)`, while still using `std.arraylist.append(&values, 420)`. thinking this would be nice ergonomically.
|
34. package declaration aliases and root `std.ArrayList` (implemented)
|
||||||
|
- bare qualified aliases use `Name :: alias package.Member` without adding a keyword
|
||||||
|
- functions/type factories, named types, and globals transparently retain the target identity;
|
||||||
|
mutable global aliases therefore share the original storage
|
||||||
|
- aliases resolve transitively at load time, consume their file-local import, preserve leading-
|
||||||
|
underscore visibility, and diagnose missing, hidden, unavailable, ambiguous, cyclic, or
|
||||||
|
conflicting targets
|
||||||
|
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
|
|||||||
@@ -251,6 +251,28 @@ Import :: struct {
|
|||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Declaration_Alias_Kind :: enum u8 {
|
||||||
|
Invalid,
|
||||||
|
Function,
|
||||||
|
Global,
|
||||||
|
Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
Declaration_Alias :: struct {
|
||||||
|
span: source.Span,
|
||||||
|
name: symbol.Id,
|
||||||
|
qualifier: symbol.Id,
|
||||||
|
member: symbol.Id,
|
||||||
|
pkg: Package_Id,
|
||||||
|
file: File_Id,
|
||||||
|
target_pkg: Package_Id,
|
||||||
|
target: u32,
|
||||||
|
kind: Declaration_Alias_Kind,
|
||||||
|
file_hidden: bool,
|
||||||
|
valid: bool,
|
||||||
|
diagnostic: source.Diagnostic_Id,
|
||||||
|
}
|
||||||
|
|
||||||
File :: struct {
|
File :: struct {
|
||||||
source: source.Source_Id,
|
source: source.Source_Id,
|
||||||
pkg: Package_Id,
|
pkg: Package_Id,
|
||||||
@@ -290,6 +312,7 @@ Module :: struct {
|
|||||||
functions: [dynamic]Function,
|
functions: [dynamic]Function,
|
||||||
globals: [dynamic]Global,
|
globals: [dynamic]Global,
|
||||||
imports: [dynamic]Import,
|
imports: [dynamic]Import,
|
||||||
|
aliases: [dynamic]Declaration_Alias,
|
||||||
files: [dynamic]File,
|
files: [dynamic]File,
|
||||||
packages: [dynamic]Package,
|
packages: [dynamic]Package,
|
||||||
unsupported: [dynamic]Unsupported,
|
unsupported: [dynamic]Unsupported,
|
||||||
@@ -309,6 +332,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
module.functions.allocator = allocator
|
module.functions.allocator = allocator
|
||||||
module.globals.allocator = allocator
|
module.globals.allocator = allocator
|
||||||
module.imports.allocator = allocator
|
module.imports.allocator = allocator
|
||||||
|
module.aliases.allocator = allocator
|
||||||
module.files.allocator = allocator
|
module.files.allocator = allocator
|
||||||
module.packages.allocator = allocator
|
module.packages.allocator = allocator
|
||||||
module.unsupported.allocator = allocator
|
module.unsupported.allocator = allocator
|
||||||
@@ -360,6 +384,7 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(module.functions)
|
delete(module.functions)
|
||||||
delete(module.globals)
|
delete(module.globals)
|
||||||
delete(module.imports)
|
delete(module.imports)
|
||||||
|
delete(module.aliases)
|
||||||
delete(module.files)
|
delete(module.files)
|
||||||
delete(module.packages)
|
delete(module.packages)
|
||||||
delete(module.unsupported)
|
delete(module.unsupported)
|
||||||
|
|||||||
@@ -868,6 +868,11 @@ build_symbol_indexes :: proc(checker: ^Checker) {
|
|||||||
function_count += 1
|
function_count += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for alias in checker.ast_module.aliases {
|
||||||
|
if alias.valid && alias.kind == .Function {
|
||||||
|
function_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator)
|
checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator)
|
||||||
function_index := 0
|
function_index := 0
|
||||||
for function, id in checker.ast_module.functions {
|
for function, id in checker.ast_module.functions {
|
||||||
@@ -877,12 +882,31 @@ build_symbol_indexes :: proc(checker: ^Checker) {
|
|||||||
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)}
|
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)}
|
||||||
function_index += 1
|
function_index += 1
|
||||||
}
|
}
|
||||||
|
for alias in checker.ast_module.aliases {
|
||||||
|
if alias.valid && alias.kind == .Function {
|
||||||
|
checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Function_Id(alias.target)}
|
||||||
|
function_index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
slice.sort_by(checker.function_index, function_index_less)
|
slice.sort_by(checker.function_index, function_index_less)
|
||||||
|
|
||||||
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
|
global_count := len(checker.ast_module.globals)
|
||||||
|
for alias in checker.ast_module.aliases {
|
||||||
|
if alias.valid && alias.kind == .Global {
|
||||||
|
global_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.global_index = make([]Global_Index_Entry, global_count, checker.allocator)
|
||||||
for global, id in checker.ast_module.globals {
|
for global, id in checker.ast_module.globals {
|
||||||
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)}
|
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)}
|
||||||
}
|
}
|
||||||
|
global_index := len(checker.ast_module.globals)
|
||||||
|
for alias in checker.ast_module.aliases {
|
||||||
|
if alias.valid && alias.kind == .Global {
|
||||||
|
checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Global_Id(alias.target)}
|
||||||
|
global_index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
slice.sort_by(checker.global_index, global_index_less)
|
slice.sort_by(checker.global_index, global_index_less)
|
||||||
|
|
||||||
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
||||||
|
|||||||
@@ -1293,6 +1293,235 @@ find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.I
|
|||||||
return ast.INVALID_IMPORT
|
return ast.INVALID_IMPORT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alias_declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
|
||||||
|
return left_file == right_file if left_hidden && right_hidden else true
|
||||||
|
}
|
||||||
|
|
||||||
|
alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool {
|
||||||
|
for function in module.functions {
|
||||||
|
if function.pkg == alias.pkg && function.name == alias.name &&
|
||||||
|
alias_declarations_conflict(alias.file, alias.file_hidden, function.file, function.file_hidden) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for global in module.globals {
|
||||||
|
if global.pkg == alias.pkg && global.name == alias.name &&
|
||||||
|
alias_declarations_conflict(alias.file, alias.file_hidden, global.file, global.file_hidden) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in module.type_store.nodes {
|
||||||
|
if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) &&
|
||||||
|
alias_declarations_conflict(alias.file, alias.file_hidden, ast.File_Id(item.file), item.file_hidden) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Declaration_Alias_Kind, u32, int) {
|
||||||
|
kind := ast.Declaration_Alias_Kind.Invalid
|
||||||
|
target: u32
|
||||||
|
kinds := 0
|
||||||
|
for function, index in module.functions {
|
||||||
|
if function.pkg == pkg && function.name == name && !function.generated && !function.file_hidden {
|
||||||
|
kind = .Function
|
||||||
|
target = u32(ast.function_id(index))
|
||||||
|
kinds += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for global, index in module.globals {
|
||||||
|
if global.pkg == pkg && global.name == name && !global.file_hidden {
|
||||||
|
kind = .Global
|
||||||
|
target = u32(ast.global_id(index))
|
||||||
|
kinds += 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value := types.find_named(&module.type_store, u32(pkg), u32(name)); types.is_valid(value) {
|
||||||
|
if item, ok := types.node(&module.type_store, value); ok && item.declared {
|
||||||
|
kind = .Type
|
||||||
|
target = u32(value)
|
||||||
|
kinds += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kind, target, kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
||||||
|
for function in module.functions {
|
||||||
|
if function.pkg == pkg && function.name == name && function.file_hidden {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for global in module.globals {
|
||||||
|
if global.pkg == pkg && global.name == name && global.file_hidden {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in module.type_store.nodes {
|
||||||
|
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.file_hidden {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for alias in module.aliases {
|
||||||
|
if alias.valid && alias.pkg == pkg && alias.name == name && alias.file_hidden {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int {
|
||||||
|
for alias, index in module.aliases {
|
||||||
|
if alias.valid && alias.pkg == pkg && alias.name == name && !alias.file_hidden {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bool {
|
||||||
|
alias := &state.module.aliases[index]
|
||||||
|
if !alias.valid {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if states[index] == 2 {
|
||||||
|
return alias.kind != .Invalid
|
||||||
|
}
|
||||||
|
if states[index] == 1 {
|
||||||
|
alias.diagnostic = source.addf(
|
||||||
|
state.diagnostics,
|
||||||
|
alias.span,
|
||||||
|
"declaration alias cycle involving '%s'",
|
||||||
|
symbol.resolve(state.symbols, alias.name),
|
||||||
|
)
|
||||||
|
alias.valid = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
states[index] = 1
|
||||||
|
defer states[index] = 2
|
||||||
|
|
||||||
|
kind, target, kinds := direct_alias_target(state.module, alias.target_pkg, alias.member)
|
||||||
|
if kinds > 1 {
|
||||||
|
alias.diagnostic = source.addf(
|
||||||
|
state.diagnostics,
|
||||||
|
alias.span,
|
||||||
|
"package member '%s.%s' is ambiguous",
|
||||||
|
symbol.resolve(state.symbols, alias.qualifier),
|
||||||
|
symbol.resolve(state.symbols, alias.member),
|
||||||
|
)
|
||||||
|
alias.valid = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if kinds == 1 {
|
||||||
|
alias.kind = kind
|
||||||
|
alias.target = target
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_alias := find_public_alias(state.module, alias.target_pkg, alias.member); target_alias >= 0 {
|
||||||
|
if resolve_declaration_alias(state, target_alias, states) {
|
||||||
|
resolved := state.module.aliases[target_alias]
|
||||||
|
alias.kind = resolved.kind
|
||||||
|
alias.target = resolved.target
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
alias.valid = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if hidden_alias_target_exists(state.module, alias.target_pkg, alias.member) {
|
||||||
|
alias.diagnostic = source.addf(
|
||||||
|
state.diagnostics,
|
||||||
|
alias.span,
|
||||||
|
"package member '%s.%s' is file-hidden",
|
||||||
|
symbol.resolve(state.symbols, alias.qualifier),
|
||||||
|
symbol.resolve(state.symbols, alias.member),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
alias.diagnostic = source.addf(
|
||||||
|
state.diagnostics,
|
||||||
|
alias.span,
|
||||||
|
"package '%s' has no member '%s'",
|
||||||
|
symbol.resolve(state.symbols, alias.qualifier),
|
||||||
|
symbol.resolve(state.symbols, alias.member),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
alias.valid = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_declaration_aliases :: proc(state: ^State) {
|
||||||
|
for &alias, index in state.module.aliases {
|
||||||
|
name := symbol.resolve(state.symbols, alias.name)
|
||||||
|
if alias_conflicts_with_declaration(state.module, alias) {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with a package declaration", name)
|
||||||
|
alias.valid = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for previous in state.module.aliases[:index] {
|
||||||
|
if previous.pkg == alias.pkg && previous.name == alias.name &&
|
||||||
|
alias_declarations_conflict(alias.file, alias.file_hidden, previous.file, previous.file_hidden) {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name)
|
||||||
|
alias.valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !alias.valid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for import_item in state.module.imports {
|
||||||
|
if import_item.file == alias.file && import_item.alias == alias.name {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with an import", name)
|
||||||
|
alias.valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !alias.valid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
import_id := find_type_import(state.module, alias.file, alias.qualifier)
|
||||||
|
if import_id == ast.INVALID_IMPORT {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown package alias '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
||||||
|
alias.valid = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state.module.imports[import_id].used = true
|
||||||
|
import_item := state.module.imports[import_id]
|
||||||
|
alias.target_pkg = import_item.target
|
||||||
|
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
|
||||||
|
int(import_item.target) >= len(state.module.packages) || !state.module.packages[import_item.target].available {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
||||||
|
alias.valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
states := make([]u8, len(state.module.aliases), state.allocator)
|
||||||
|
defer delete(states, state.allocator)
|
||||||
|
for _, index in state.module.aliases {
|
||||||
|
_ = resolve_declaration_alias(state, index, states)
|
||||||
|
}
|
||||||
|
for &alias in state.module.aliases {
|
||||||
|
if !alias.valid || alias.kind != .Type {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := types.named(
|
||||||
|
&state.module.type_store,
|
||||||
|
u32(alias.pkg),
|
||||||
|
u32(alias.name),
|
||||||
|
file=u32(alias.file),
|
||||||
|
file_hidden=alias.file_hidden,
|
||||||
|
)
|
||||||
|
if !types.define_alias(&state.module.type_store, id, types.Type(alias.target)) {
|
||||||
|
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name))
|
||||||
|
alias.valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
canonical_type :: proc(
|
canonical_type :: proc(
|
||||||
module: ^ast.Module,
|
module: ^ast.Module,
|
||||||
value: types.Type,
|
value: types.Type,
|
||||||
@@ -1456,6 +1685,7 @@ load :: proc(
|
|||||||
state.root_failed = true
|
state.root_failed = true
|
||||||
}
|
}
|
||||||
validate_imports(&state)
|
validate_imports(&state)
|
||||||
|
validate_declaration_aliases(&state)
|
||||||
canonicalize_types(&module, allocator)
|
canonicalize_types(&module, allocator)
|
||||||
return module, !state.root_failed
|
return module, !state.root_failed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2425,6 +2425,31 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
|
|
||||||
parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
parse_alias :: proc(parser: ^Parser, name: token.Token) {
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
|
saved := parser.cursor
|
||||||
|
if current(parser).kind == .Identifier && peek(parser).kind == .Dot {
|
||||||
|
qualifier := advance(parser)
|
||||||
|
advance(parser)
|
||||||
|
if current(parser).kind == .Identifier {
|
||||||
|
member := advance(parser)
|
||||||
|
if current(parser).kind == .Newline || current(parser).kind == .Eof {
|
||||||
|
append(&parser.module.aliases, ast.Declaration_Alias{
|
||||||
|
span=span_from(name.span, member.span),
|
||||||
|
name=name.symbol,
|
||||||
|
qualifier=qualifier.symbol,
|
||||||
|
member=member.symbol,
|
||||||
|
pkg=parser.pkg,
|
||||||
|
file=parser.file,
|
||||||
|
target_pkg=ast.INVALID_PACKAGE,
|
||||||
|
file_hidden=file_hidden_name(parser, name),
|
||||||
|
valid=true,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
_ = finish_statement(parser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parser.cursor = saved
|
||||||
child := parse_type(parser)
|
child := parse_type(parser)
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
|
||||||
if !types.define_alias(&parser.module.type_store, id, child) {
|
if !types.define_alias(&parser.module.type_store, id, child) {
|
||||||
|
|||||||
@@ -7125,6 +7125,149 @@ imports_do_not_reexport_members :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
declaration_aliases_preserve_identity_and_chain :: proc(t: ^testing.T) {
|
||||||
|
root :: "/tmp/brolang-test-declaration-aliases"
|
||||||
|
dep_dir :: root + "/dep"
|
||||||
|
facade_dir :: root + "/facade"
|
||||||
|
top_dir :: root + "/top"
|
||||||
|
app_dir :: root + "/app"
|
||||||
|
output :: "/tmp/brolang-test-declaration-aliases-output"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
directories := [?]string{root, dep_dir, facade_dir, top_dir, app_dir}
|
||||||
|
for directory in directories {
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
}
|
||||||
|
dep_text := `Box func($T type) type {
|
||||||
|
return struct { value T }
|
||||||
|
}
|
||||||
|
Point :: struct { value i32 }
|
||||||
|
counter i32 = 1
|
||||||
|
answer func() i32 { return 40 }
|
||||||
|
`
|
||||||
|
facade_text := `dep :: import "../dep"
|
||||||
|
RenamedBox :: alias dep.Box
|
||||||
|
RenamedPoint :: alias dep.Point
|
||||||
|
counter :: alias dep.counter
|
||||||
|
answer :: alias dep.answer
|
||||||
|
_local_answer :: alias dep.answer
|
||||||
|
local_answer func() i32 { return _local_answer() }
|
||||||
|
Scalar :: alias i32
|
||||||
|
MaybePoint :: alias ?@dep.Point
|
||||||
|
Concrete :: alias dep.Box(i32)
|
||||||
|
`
|
||||||
|
top_text := `facade :: import "../facade"
|
||||||
|
Box :: alias facade.RenamedBox
|
||||||
|
Point :: alias facade.RenamedPoint
|
||||||
|
counter :: alias facade.counter
|
||||||
|
answer :: alias facade.answer
|
||||||
|
`
|
||||||
|
app_text := `dep :: import "../dep"
|
||||||
|
facade :: import "../facade"
|
||||||
|
top :: import "../top"
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
box top.Box(i32) :: top.Box(i32) { value = 2 }
|
||||||
|
point top.Point :: top.Point { value = 3 }
|
||||||
|
maybe facade.MaybePoint :: none
|
||||||
|
scalar facade.Scalar :: 5
|
||||||
|
top.counter = 7
|
||||||
|
if box.value != 2 or point.value != 3 { return 1 }
|
||||||
|
if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 }
|
||||||
|
_ = maybe
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(dep_dir + "/dep.bro", transmute([]byte)dep_text))
|
||||||
|
testing.expect(t, os.write_entire_file(facade_dir + "/facade.bro", transmute([]byte)facade_text))
|
||||||
|
testing.expect(t, os.write_entire_file(top_dir + "/top.bro", transmute([]byte)top_text))
|
||||||
|
testing.expect(t, os.write_entire_file(app_dir + "/main.bro", transmute([]byte)app_text))
|
||||||
|
|
||||||
|
status := compiler_core.compile_package(app_dir, output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
|
||||||
|
root :: "/tmp/brolang-test-declaration-alias-errors"
|
||||||
|
dep_dir :: root + "/dep"
|
||||||
|
facade_dir :: root + "/facade"
|
||||||
|
a_dir :: root + "/a"
|
||||||
|
b_dir :: root + "/b"
|
||||||
|
app_dir :: root + "/app"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
directories := [?]string{root, dep_dir, facade_dir, a_dir, b_dir, app_dir}
|
||||||
|
for directory in directories {
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
}
|
||||||
|
dep_text := `visible func() i32 { return 1 }
|
||||||
|
_hidden func() i32 { return 2 }
|
||||||
|
ambiguous func() i32 { return 3 }
|
||||||
|
ambiguous i32 :: 4
|
||||||
|
`
|
||||||
|
facade_text := `dep :: import "../dep"
|
||||||
|
gone :: import "../gone"
|
||||||
|
missing :: alias dep.missing
|
||||||
|
hidden :: alias dep._hidden
|
||||||
|
unknown :: alias nope.visible
|
||||||
|
unavailable :: alias gone.visible
|
||||||
|
ambiguous :: alias dep.ambiguous
|
||||||
|
duplicate :: alias dep.visible
|
||||||
|
duplicate :: alias dep.visible
|
||||||
|
collision func() i32 { return 0 }
|
||||||
|
collision :: alias dep.visible
|
||||||
|
dep :: alias dep.visible
|
||||||
|
`
|
||||||
|
a_text := `b :: import "../b"
|
||||||
|
value :: alias b.value
|
||||||
|
`
|
||||||
|
b_text := `a :: import "../a"
|
||||||
|
value :: alias a.value
|
||||||
|
`
|
||||||
|
app_text := `facade :: import "../facade"
|
||||||
|
a :: import "../a"
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(dep_dir + "/dep.bro", transmute([]byte)dep_text))
|
||||||
|
testing.expect(t, os.write_entire_file(facade_dir + "/facade.bro", transmute([]byte)facade_text))
|
||||||
|
testing.expect(t, os.write_entire_file(a_dir + "/a.bro", transmute([]byte)a_text))
|
||||||
|
testing.expect(t, os.write_entire_file(b_dir + "/b.bro", transmute([]byte)b_text))
|
||||||
|
testing.expect(t, os.write_entire_file(app_dir + "/main.bro", transmute([]byte)app_text))
|
||||||
|
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(app_dir, &sources, &diagnostics, &symbols)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
wants := []string{
|
||||||
|
"has no member 'missing'",
|
||||||
|
"is file-hidden",
|
||||||
|
"unknown package alias 'nope'",
|
||||||
|
"unavailable imported package 'gone'",
|
||||||
|
"package member 'dep.ambiguous' is ambiguous",
|
||||||
|
"duplicate declaration alias 'duplicate'",
|
||||||
|
"declaration alias 'collision' conflicts with a package declaration",
|
||||||
|
"declaration alias 'dep' conflicts with an import",
|
||||||
|
"declaration alias cycle",
|
||||||
|
}
|
||||||
|
for want in wants {
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, want)
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
self_import_via_dot_is_valid :: proc(t: ^testing.T) {
|
self_import_via_dot_is_valid :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-package-self"
|
output := "/tmp/brolang-test-package-self"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
arraylist :: import "@std/arraylist"
|
arraylist :: import "@std/arraylist"
|
||||||
mem :: import "@std/mem"
|
mem :: import "@std/mem"
|
||||||
|
std :: import "@std"
|
||||||
|
|
||||||
_fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
_fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||||
return none
|
return none
|
||||||
@@ -25,7 +26,7 @@ _fail_allocator mem.Allocator :: mem.Allocator {
|
|||||||
_noop func() void {}
|
_noop func() void {}
|
||||||
|
|
||||||
run func() i32 ! mem.AllocError {
|
run func() i32 ! mem.AllocError {
|
||||||
values arraylist.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||||
defer arraylist.deinit(&values)
|
defer arraylist.deinit(&values)
|
||||||
if (values.items.len != 0 or values.capacity != 0) return 1
|
if (values.items.len != 0 or values.capacity != 0) return 1
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "arraylist"
|
||||||
|
|
||||||
|
ArrayList :: alias arraylist.ArrayList
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
arraylist :: import "@std/arraylist"
|
import "@ffi/c"
|
||||||
mem :: import "@std/mem"
|
import "@std"
|
||||||
c :: import "@ffi/c"
|
import "@std/mem"
|
||||||
|
import "@std/arraylist"
|
||||||
|
|
||||||
Kind :: enum(u8) {
|
Kind :: enum(u8) {
|
||||||
invalid
|
invalid
|
||||||
@@ -37,7 +38,7 @@ _word_kind func(word []u8) Kind {
|
|||||||
return .identifier
|
return .identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
_append func(tokens @mut arraylist.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
_append func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
||||||
try arraylist.append(tokens, Token {
|
try arraylist.append(tokens, Token {
|
||||||
start = start,
|
start = start,
|
||||||
length = end - start,
|
length = end - start,
|
||||||
@@ -46,7 +47,7 @@ _append func(tokens @mut arraylist.ArrayList(Token), kind Kind, start, end usize
|
|||||||
return _
|
return _
|
||||||
}
|
}
|
||||||
|
|
||||||
lex func(source []u8, tokens @mut arraylist.ArrayList(Token)) void ! mem.AllocError {
|
lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
||||||
cursor usize = 0
|
cursor usize = 0
|
||||||
while cursor < source.len {
|
while cursor < source.len {
|
||||||
value u8 :: source[cursor]
|
value u8 :: source[cursor]
|
||||||
@@ -131,7 +132,7 @@ main func() i32 {
|
|||||||
` hello()
|
` hello()
|
||||||
`}
|
`}
|
||||||
|
|
||||||
tokens arraylist.ArrayList(Token) = arraylist.init(mem.c_allocator)
|
tokens std.ArrayList(Token) = arraylist.init(mem.c_allocator)
|
||||||
defer arraylist.deinit(&tokens)
|
defer arraylist.deinit(&tokens)
|
||||||
|
|
||||||
lex(source, &tokens) catch |_| {
|
lex(source, &tokens) catch |_| {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
|||||||
|
|
||||||
new_capacity usize = 8
|
new_capacity usize = 8
|
||||||
if list.capacity >= 8 {
|
if list.capacity >= 8 {
|
||||||
half usize :: list.capacity / 2
|
half usize :: div_trunc(list.capacity, 2)
|
||||||
if list.capacity > max_value(usize) - half {
|
if list.capacity > max_value(usize) - half {
|
||||||
new_capacity = minimum_capacity
|
new_capacity = minimum_capacity
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
c :: import "@ffi/c"
|
||||||
|
|
||||||
|
ReadError :: enum {
|
||||||
|
read_failed
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteError :: enum {
|
||||||
|
write_failed
|
||||||
|
no_progress
|
||||||
|
}
|
||||||
|
|
||||||
|
Io :: struct {
|
||||||
|
context ?*mut anyopaque
|
||||||
|
vtable @IoVTable
|
||||||
|
}
|
||||||
|
|
||||||
|
IoVTable :: struct {
|
||||||
|
read @func(context ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError
|
||||||
|
write @func(context ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadStream :: enum(c_int) {
|
||||||
|
stdin = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteStream :: enum(c_int) {
|
||||||
|
stdout = 1
|
||||||
|
stderr = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader :: struct {
|
||||||
|
impl Io
|
||||||
|
stream ReadStream
|
||||||
|
}
|
||||||
|
|
||||||
|
Writer :: struct {
|
||||||
|
impl Io
|
||||||
|
stream WriteStream
|
||||||
|
}
|
||||||
|
|
||||||
|
read func(reader Reader, buffer []mut u8) usize ! ReadError {
|
||||||
|
if buffer.len == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
count usize :: try reader.impl.vtable.read(reader.impl.context, reader.stream, buffer)
|
||||||
|
if count > buffer.len {
|
||||||
|
return .read_failed
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
write func(writer Writer, bytes []u8) usize ! WriteError {
|
||||||
|
if bytes.len == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
count usize :: try writer.impl.vtable.write(writer.impl.context, writer.stream, bytes)
|
||||||
|
if count > bytes.len {
|
||||||
|
return .write_failed
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||||
|
offset usize = 0
|
||||||
|
while offset < bytes.len {
|
||||||
|
count usize :: write(writer, bytes[offset..]) catch |err| {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return .no_progress
|
||||||
|
}
|
||||||
|
offset += count
|
||||||
|
}
|
||||||
|
return _
|
||||||
|
}
|
||||||
|
|
||||||
|
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||||
|
request usize = buffer.len
|
||||||
|
maximum usize :: usize(max_value(c_long))
|
||||||
|
if request > maximum {
|
||||||
|
request = maximum
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
count c_long :: c.read(c_int(stream), buffer.ptr, c_ulong(request))
|
||||||
|
if count >= 0 {
|
||||||
|
return usize(count)
|
||||||
|
}
|
||||||
|
if c.__error()^ != 4 {
|
||||||
|
return .read_failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||||
|
fd c_int :: c_int(stream)
|
||||||
|
request usize = bytes.len
|
||||||
|
maximum usize :: usize(max_value(c_long))
|
||||||
|
if request > maximum {
|
||||||
|
request = maximum
|
||||||
|
}
|
||||||
|
while true {
|
||||||
|
count c_long :: c.write(fd, bytes.ptr, c_ulong(request))
|
||||||
|
if count >= 0 {
|
||||||
|
return usize(count)
|
||||||
|
}
|
||||||
|
if c.__error()^ != 4 {
|
||||||
|
return .write_failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_system_vtable IoVTable :: IoVTable {
|
||||||
|
read = _system_read,
|
||||||
|
write = _system_write,
|
||||||
|
}
|
||||||
|
|
||||||
|
_system func() Io {
|
||||||
|
return Io {
|
||||||
|
context = none,
|
||||||
|
vtable = &_system_vtable,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
|||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, count)
|
return _empty_slice(T, count)
|
||||||
}
|
}
|
||||||
if count > max_value(usize) / element_size {
|
if count > div_trunc(max_value(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
if element_size == 0 {
|
if element_size == 0 {
|
||||||
return _empty_slice(T, new_count)
|
return _empty_slice(T, new_count)
|
||||||
}
|
}
|
||||||
if new_count > max_value(usize) / element_size {
|
if new_count > div_trunc(max_value(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ _power_of_two func(value usize) bool {
|
|||||||
|
|
||||||
current usize = value
|
current usize = value
|
||||||
while current > 1 {
|
while current > 1 {
|
||||||
half usize = current / 2
|
half usize = div_trunc(current, 2)
|
||||||
if half * 2 != current {
|
if half * 2 != current {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import "arraylist"
|
||||||
|
|
||||||
|
ArrayList :: alias arraylist.ArrayList
|
||||||
Reference in New Issue
Block a user