feat: support enums, distinct types, unions and matching
This commit is contained in:
+1091
-51
File diff suppressed because it is too large
Load Diff
@@ -69,8 +69,8 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`forward float :: -alias * 2 + seed
|
||||
`alias :: seed
|
||||
`forward float :: -forwarded * 2 + seed
|
||||
`forwarded :: seed
|
||||
`seed := 3
|
||||
`backward :: 4
|
||||
`demand float :: backward
|
||||
@@ -81,7 +81,7 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
||||
`text :: "honey"
|
||||
`fixed int :: 7
|
||||
`anchored :: fixed + 2
|
||||
`nested float :: quotient / (alias + 1)
|
||||
`nested float :: quotient / (forwarded + 1)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
@@ -192,9 +192,9 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`alias :: alpha
|
||||
`forwarded :: alpha
|
||||
`alpha proc(a, b int) void {
|
||||
` (alias)(a, b)
|
||||
` (forwarded)(a, b)
|
||||
` beta(a + b, seed)
|
||||
`}
|
||||
`beta proc(total int, amount float) void {
|
||||
@@ -203,7 +203,7 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
||||
`seed := 3
|
||||
`main proc() void {
|
||||
` beta(1, 2)
|
||||
` (alias)(3, 4)
|
||||
` (forwarded)(3, 4)
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
@@ -359,8 +359,8 @@ reports_nonvoid_fallthrough_without_poisoning_signatures_and_checks_unused_bodie
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`answer :: alias()
|
||||
`alias :: make
|
||||
`answer :: forwarded()
|
||||
`forwarded :: make
|
||||
`make proc() int {}
|
||||
`unused proc() float {}
|
||||
`broken proc() void {
|
||||
@@ -762,13 +762,13 @@ checks_bool_comparisons_recursive_returns_and_nested_numeric_context test {
|
||||
`flag bool :: true
|
||||
`same :: flag == false
|
||||
`different :: true != flag
|
||||
`less :: alias < 4.0
|
||||
`less :: forwarded < 4.0
|
||||
`less_equal :: seed <= 5
|
||||
`greater :: 6 > seed
|
||||
`greater_equal :: seed >= 1
|
||||
`equal :: seed == 3
|
||||
`not_equal :: seed != 2
|
||||
`alias :: seed
|
||||
`forwarded :: seed
|
||||
`seed := 3
|
||||
`identity proc(value bool) bool { return value }
|
||||
`factorial proc(n int) int {
|
||||
@@ -1789,11 +1789,11 @@ range_checks_both_bounds_and_preserves_fixed_declarations_and_source_errors test
|
||||
source ::
|
||||
`real float :: 2
|
||||
`flag :: true
|
||||
`alias :: flag
|
||||
`forwarded :: flag
|
||||
`nothing proc() void {}
|
||||
`main proc() void {
|
||||
` for 1.5..2.5 |_| {}
|
||||
` for alias.."text" |_| {}
|
||||
` for forwarded.."text" |_| {}
|
||||
` for nothing..nothing() |_| {}
|
||||
` for real..3 |_| {}
|
||||
` for absent_start..absent_end |_| {}
|
||||
@@ -2206,8 +2206,8 @@ expect_array proc(result @Result, id ast.NodeId, len usize, mutable bool, elemen
|
||||
|
||||
fixed_arrays_contextualize_forward_aliases_counts_nested_literals_and_signatures test {
|
||||
source ::
|
||||
`context [2]f64 :: alias
|
||||
`alias :: seed
|
||||
`context [2]f64 :: forwarded
|
||||
`forwarded :: seed
|
||||
`seed := [1, 2]
|
||||
`count :: later * 2 - 1
|
||||
`later :: 2
|
||||
|
||||
+91
-1
@@ -1,6 +1,7 @@
|
||||
import "@std/debug"
|
||||
import "@std/mem"
|
||||
import "@std/hashmap"
|
||||
import "@std/arraylist"
|
||||
import "@source/strpool"
|
||||
|
||||
import "@source/ast"
|
||||
@@ -99,6 +100,8 @@ State :: struct {
|
||||
builtin_ids []strpool.StringId
|
||||
allocator mem.Allocator
|
||||
namespaces hashmap.AutoHashMap(ast.NodeId, Scope)
|
||||
aliases []mut ast.NodeId
|
||||
selections arraylist.ArrayList(ast.NodeId)
|
||||
}
|
||||
|
||||
#! borrows `tree`, its identifier `pool`, and `diagnostic_store` during resolution.
|
||||
@@ -138,6 +141,9 @@ resolve_packages proc(
|
||||
errdefer mem.free(allocator, bindings)
|
||||
mem.set(bindings, Binding{})
|
||||
builtin_ids :: try intern_builtins(pool, &builtins)
|
||||
aliases :: try mem.alloc(ast.NodeId, allocator, ast.node_count(tree))
|
||||
defer mem.free(allocator, aliases)
|
||||
mem.set(aliases, maxval!(ast.NodeId))
|
||||
|
||||
state := State{
|
||||
tree = tree,
|
||||
@@ -146,7 +152,10 @@ resolve_packages proc(
|
||||
builtin_ids = builtin_ids[..],
|
||||
allocator = allocator,
|
||||
namespaces = hashmap.init(allocator),
|
||||
aliases = aliases,
|
||||
selections = arraylist.init(allocator),
|
||||
}
|
||||
defer arraylist.deinit(&state.selections)
|
||||
defer {
|
||||
for (state.namespaces.entries) |entry| {
|
||||
if (entry.hash == 0) continue
|
||||
@@ -184,6 +193,7 @@ resolve_packages proc(
|
||||
}
|
||||
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
||||
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
||||
try canonicalize_aliases(&state)
|
||||
|
||||
return Resolution{ storage = Storage{
|
||||
bindings = bindings,
|
||||
@@ -402,10 +412,62 @@ resolve_statement_decl proc(
|
||||
id ast.NodeId,
|
||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
declaration :: ast.statement_decl(state.tree, id)
|
||||
if (ast.expr_kind(state.tree, declaration.initializer) == .alias_expr) {
|
||||
state.aliases[usize(id)] = ast.type_operand(state.tree, declaration.initializer)
|
||||
}
|
||||
if (declaration.type) |type| try resolve_expr(state, scope, type)
|
||||
try resolve_expr(state, scope, declaration.initializer)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
canonical_binding proc(state @State, original Binding) Binding {
|
||||
value := original
|
||||
for (0..(state.aliases.len)) |_| {
|
||||
if (value.kind != .declaration) break
|
||||
operand :: state.aliases[usize(value.data.declaration)]
|
||||
if (operand == maxval!(ast.NodeId)) break
|
||||
target :: state.bindings[usize(operand)]
|
||||
if (target.kind == .none) break
|
||||
if (target.kind == .declaration and target.data.declaration == value.data.declaration) break
|
||||
value = target
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@hide:file
|
||||
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
# aliases may forward-reference other aliases or imported namespace members.
|
||||
for (0..(state.aliases.len)) |_| {
|
||||
changed := false
|
||||
for (state.bindings) |@entry| {
|
||||
value :: canonical_binding(state, entry^)
|
||||
if (value.kind != entry.kind) changed = true
|
||||
else if (value.kind == .declaration and value.data.declaration != entry.data.declaration) changed = true
|
||||
entry^ = value
|
||||
}
|
||||
for (state.selections.items) |id| {
|
||||
expression :: ast.selection_expr(state.tree, id)
|
||||
base :: state.bindings[usize(expression.base)]
|
||||
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
|
||||
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
|
||||
else
|
||||
null
|
||||
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
||||
kind = .invalid,
|
||||
data = BindingData{ diagnostic = try diagnostics.add(
|
||||
state.diagnostics, .unknown_name, ast.node_span(state.tree, expression.member),
|
||||
) },
|
||||
}
|
||||
state.bindings[usize(id)] = value
|
||||
state.bindings[usize(expression.member)] = value
|
||||
changed = true
|
||||
}
|
||||
if (!changed) break
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
resolve_invalid proc(state @mut State, id ast.NodeId) void {
|
||||
if (state.bindings[usize(id)].kind != .none) return
|
||||
@@ -426,6 +488,30 @@ resolve_expr proc(
|
||||
tree :: state.tree
|
||||
match ast.expr_kind(tree, id) {
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string: {}
|
||||
.enum_literal: {}
|
||||
.distinct_type, .alias_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.enum_type: {
|
||||
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
||||
for (0..ast.enum_member_count(tree, id)) |i| {
|
||||
member :: ast.field(tree, ast.enum_member(tree, id, i))
|
||||
if (member.value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
}
|
||||
.match_expr: {
|
||||
try resolve_expr(state, scope, ast.match_expr(tree, id).subject)
|
||||
for (0..ast.match_arm_count(tree, id)) |i| {
|
||||
arm_id :: ast.match_arm(tree, id, i)
|
||||
arm :: ast.match_arm_view(tree, arm_id)
|
||||
for (0..ast.match_pattern_count(tree, arm_id)) |j| {
|
||||
try resolve_expr(state, scope, ast.match_pattern(tree, arm_id, j))
|
||||
}
|
||||
arm_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
||||
defer hashmap.deinit(&arm_scope.names)
|
||||
if (arm.capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
||||
if (arm.expression) try resolve_expr(state, &arm_scope, arm.body)
|
||||
else try resolve_block(state, &arm_scope, arm.body)
|
||||
}
|
||||
}
|
||||
.identifier: {
|
||||
span :: ast.node_span(tree, id)
|
||||
name :: ast.name_id(tree, id)
|
||||
@@ -441,6 +527,7 @@ resolve_expr proc(
|
||||
}
|
||||
.selection: {
|
||||
expression :: ast.selection_expr(tree, id)
|
||||
try arraylist.append(&state.selections, id)
|
||||
try resolve_expr(state, scope, expression.base)
|
||||
base :: state.bindings[usize(expression.base)]
|
||||
value := Binding{}
|
||||
@@ -494,10 +581,13 @@ resolve_expr proc(
|
||||
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
||||
}
|
||||
}
|
||||
.record_type, .record_literal: {
|
||||
.record_type, .record_literal, .union_type: {
|
||||
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
||||
}
|
||||
if (ast.expr_kind(tree, id) == .union_type) {
|
||||
if (ast.union_type(tree, id).tag) |tag| try resolve_expr(state, scope, tag)
|
||||
}
|
||||
for (0..ast.record_field_count(tree, id)) |i| {
|
||||
field :: ast.field(tree, ast.record_field(tree, id, i))
|
||||
if (field.type) |type| try resolve_expr(state, scope, type)
|
||||
|
||||
@@ -809,3 +809,126 @@ resolves_interleaved_compilations_with_independent_identifier_orders test {
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&first_diagnostics))
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&second_diagnostics))
|
||||
}
|
||||
|
||||
resolves_match_capture_scopes_without_looking_up_member_names test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Tag :: enum { if, else }
|
||||
`Value :: union(Tag) { if i32, else void }
|
||||
`inspect proc(value Value) void {
|
||||
` match value {
|
||||
` .if |payload|: { local :: payload }
|
||||
` .else: { local :: 1 }
|
||||
` }
|
||||
` payload
|
||||
` local
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &pool, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
resolution := try resolve(mem.c_allocator, &tree, &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
|
||||
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||
first :: ast.match_arm(&tree, matched, 0)
|
||||
arm :: ast.match_arm_view(&tree, first)
|
||||
local :: ast.statement_decl(&tree, ast.block_statement(&tree, arm.body, 0))
|
||||
try testing.expect_equal(arm.capture?, binding(&resolution, local.initializer).data.declaration)
|
||||
member :: ast.enum_literal(&tree, ast.match_pattern(&tree, first, 0))
|
||||
try testing.expect_equal(BindingKind.none, binding(&resolution, member).kind)
|
||||
for (usize(1)..usize(3)) |index| {
|
||||
reference :: ast.statement_expr(&tree, ast.block_statement(&tree, body, index))
|
||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, reference).kind)
|
||||
}
|
||||
}
|
||||
|
||||
canonicalizes_forward_function_global_and_builtin_aliases test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`invoke :: alias call
|
||||
`copy :: alias storage
|
||||
`Number :: alias i32
|
||||
`call :: alias original
|
||||
`original proc() i32 { return storage }
|
||||
`storage i32 := 1
|
||||
`main proc() Number {
|
||||
` copy = invoke()
|
||||
` return copy
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &pool, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try resolve(mem.c_allocator, &tree, &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
original :: ast.package_decl(&tree, 4)
|
||||
storage :: ast.package_decl(&tree, 5)
|
||||
main :: ast.function_decl(&tree, ast.package_decl(&tree, 6))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||
try testing.expect_equal(BindingKind.builtin, binding(&resolution, main.return_type).kind)
|
||||
try testing.expect_equal(BuiltinType.i32, binding(&resolution, main.return_type).data.builtin)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.package_decl(&tree, 0)).data.declaration)
|
||||
}
|
||||
|
||||
canonicalizes_imported_aliases_and_namespace_alias_selections test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
builder := ast.init_builder(mem.c_allocator, "")
|
||||
defer ast.deinit_builder(&builder)
|
||||
library_source :: "original proc() i32 { return storage }\nstorage i32 := 1\nexported :: alias original\n"
|
||||
library_id :: try ast.set_source(&builder, "library.hon", library_source)
|
||||
library_scan := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&library_scan)
|
||||
library_scan.source_id = library_id
|
||||
try lexer.scan(&library_scan, library_source, &pool, &diagnostic_store)
|
||||
library :: try parser.parse_source(&builder, library_scan.tokens.items, &diagnostic_store)
|
||||
main_source ::
|
||||
`lib :: import "library"
|
||||
`invoke :: alias namespace.exported
|
||||
`copy :: alias namespace.storage
|
||||
`namespace :: alias lib
|
||||
`main proc() i32 { copy = invoke()
|
||||
` return copy
|
||||
`}
|
||||
main_id :: try ast.set_source(&builder, "main.hon", main_source)
|
||||
main_scan := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&main_scan)
|
||||
main_scan.source_id = main_id
|
||||
try lexer.scan(&main_scan, main_source, &pool, &diagnostic_store)
|
||||
root :: try parser.parse_source(&builder, main_scan.tokens.items, &diagnostic_store)
|
||||
tree := ast.finish(&builder, root)
|
||||
defer ast.deinit(&tree)
|
||||
roots [2]ast.NodeId :: [root, library]
|
||||
imports [1]Import :: [Import{
|
||||
node = ast.package_decl_at(&tree, root, 0),
|
||||
name = strpool.get_id(&pool, "lib")?,
|
||||
target = library,
|
||||
}]
|
||||
resolution := try resolve_packages(mem.c_allocator, &tree, roots[..], imports[..], &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
original :: ast.package_decl_at(&tree, library, 0)
|
||||
storage :: ast.package_decl_at(&tree, library, 1)
|
||||
main :: ast.function_decl(&tree, ast.package_decl_at(&tree, root, 4))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||
namespace :: ast.package_decl_at(&tree, root, 3)
|
||||
try testing.expect_equal(BindingKind.namespace, binding(&resolution, namespace).kind)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user