function literals (non-capturing)
This commit is contained in:
@@ -104,6 +104,7 @@ Expr_Kind :: enum u8 {
|
||||
Call,
|
||||
Try,
|
||||
Catch,
|
||||
Function_Literal,
|
||||
}
|
||||
|
||||
Expr :: struct {
|
||||
@@ -209,6 +210,7 @@ Function :: struct {
|
||||
file: File_Id,
|
||||
c_abi: bool,
|
||||
imported: bool,
|
||||
generated: bool,
|
||||
has_body: bool,
|
||||
variadic: bool,
|
||||
params: []Param,
|
||||
|
||||
@@ -518,9 +518,20 @@ find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name
|
||||
}
|
||||
|
||||
build_symbol_indexes :: proc(checker: ^Checker) {
|
||||
checker.function_index = make([]Function_Index_Entry, len(checker.ast_module.functions), checker.allocator)
|
||||
function_count := 0
|
||||
for function in checker.ast_module.functions {
|
||||
if !function.generated {
|
||||
function_count += 1
|
||||
}
|
||||
}
|
||||
checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator)
|
||||
function_index := 0
|
||||
for function, id in checker.ast_module.functions {
|
||||
checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
|
||||
if function.generated {
|
||||
continue
|
||||
}
|
||||
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
|
||||
function_index += 1
|
||||
}
|
||||
slice.sort_by(checker.function_index, function_index_less)
|
||||
|
||||
@@ -1038,6 +1049,12 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
append(&stack, expr.right)
|
||||
}
|
||||
mark_block_imports_used(checker, expr.body, file)
|
||||
case .Function_Literal:
|
||||
function_id := ast.Function_Id(u32(expr.integer))
|
||||
if function_id != ast.INVALID_FUNCTION && int(function_id) < len(checker.ast_module.functions) {
|
||||
function := checker.ast_module.functions[function_id]
|
||||
mark_block_imports_used(checker, function.body, function.file)
|
||||
}
|
||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
append(&stack, expr.left, expr.right)
|
||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name:
|
||||
@@ -1865,6 +1882,11 @@ infer_expr :: proc(
|
||||
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
||||
_ = pop(&stack)
|
||||
case .Function_Literal:
|
||||
template := ast.Function_Id(u32(expr.integer))
|
||||
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
|
||||
last = pointer_type if ok else types.INVALID
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
last = types.INVALID
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
@@ -2421,7 +2443,7 @@ infer_statements :: proc(
|
||||
if statement.expr != ast.INVALID_EXPR {
|
||||
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
if is_runtime_type(checker, result_hint) {
|
||||
_ = record_demand(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||
_ = record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||
expr := checker.ast_module.exprs[statement.expr]
|
||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
|
||||
@@ -2433,7 +2455,7 @@ infer_statements :: proc(
|
||||
returned = result_hint
|
||||
} else {
|
||||
// `return G` for a global const: demand the result type onto it.
|
||||
record_demand(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||
record_demand_shallow(checker, statement.expr, result_hint, locals^[:], local_types, pkg, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2861,6 +2883,49 @@ expr_accepts_numeric_demand :: proc(
|
||||
return false
|
||||
}
|
||||
|
||||
DEMAND_RECURSION_LIMIT :: 4096
|
||||
|
||||
record_demand_too_deep :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> bool {
|
||||
stack: [dynamic]ast.Expr_Id
|
||||
stack.allocator = checker.allocator
|
||||
defer delete(stack)
|
||||
append(&stack, expr_id)
|
||||
seen := 0
|
||||
for len(stack) > 0 {
|
||||
current := pop(&stack)
|
||||
if current == ast.INVALID_EXPR || int(current) >= len(checker.ast_module.exprs) {
|
||||
continue
|
||||
}
|
||||
seen += 1
|
||||
if seen > DEMAND_RECURSION_LIMIT {
|
||||
return true
|
||||
}
|
||||
expr := checker.ast_module.exprs[current]
|
||||
#partial switch expr.kind {
|
||||
case .Negate:
|
||||
append(&stack, expr.left)
|
||||
case .Add, .Sub, .Mul, .Div:
|
||||
append(&stack, expr.left, expr.right)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
record_demand_shallow :: proc(
|
||||
checker: ^Checker,
|
||||
expr_id: ast.Expr_Id,
|
||||
demand: types.Type,
|
||||
locals: []Infer_Local,
|
||||
local_types: []types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> bool {
|
||||
if record_demand_too_deep(checker, expr_id) {
|
||||
return false
|
||||
}
|
||||
return record_demand(checker, expr_id, demand, locals, local_types, pkg, file)
|
||||
}
|
||||
|
||||
// record_demand pushes a concrete type demand onto open numeric slots reachable
|
||||
// through bare names and numeric arithmetic. Calls remain a boundary (milestone 14.5).
|
||||
record_demand :: proc(
|
||||
@@ -4569,6 +4634,10 @@ build_expr :: proc(
|
||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||
)
|
||||
_ = pop(&stack)
|
||||
case .Function_Literal:
|
||||
template := ast.Function_Id(u32(expr.integer))
|
||||
last = build_function_value(checker, template, expr.span, frame.expected)
|
||||
_ = pop(&stack)
|
||||
case .Type:
|
||||
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
@@ -5234,6 +5303,9 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
||||
if function.pkg == 0 && function.name == checker.main_symbol {
|
||||
return fmt.aprintf("main", allocator = checker.allocator)
|
||||
}
|
||||
if function.generated {
|
||||
return fmt.aprintf("bro__p%d__anon%d", function.pkg, spec.template, allocator = checker.allocator)
|
||||
}
|
||||
if !function.has_body && function.c_abi {
|
||||
if len(function.link_name) > 0 {
|
||||
return strings.clone(function.link_name, checker.allocator)
|
||||
@@ -8423,8 +8495,11 @@ check :: proc(
|
||||
}
|
||||
|
||||
for function, index in ast_module.functions {
|
||||
if function.generated {
|
||||
continue
|
||||
}
|
||||
for previous in ast_module.functions[:index] {
|
||||
if previous.pkg == function.pkg && previous.name == function.name {
|
||||
if !previous.generated && previous.pkg == function.pkg && previous.name == function.name {
|
||||
source.addf(diagnostics, function.span, "duplicate function '%s'", symbol_text(&checker, function.name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,6 +736,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Keyword_Func:
|
||||
return parse_function_literal(parser)
|
||||
case .Left_Bracket:
|
||||
return parse_array_literal(parser, nesting)
|
||||
case .Dot:
|
||||
@@ -2111,6 +2113,56 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
})
|
||||
}
|
||||
|
||||
parse_function_literal :: proc(parser: ^Parser) -> ast.Expr_Id {
|
||||
start := advance(parser)
|
||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
||||
}
|
||||
params, variadic := parse_params(parser)
|
||||
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after parameters")
|
||||
}
|
||||
skip_newlines(parser)
|
||||
result := parse_type(parser)
|
||||
error_type := types.INVALID
|
||||
if _, ok := allow(parser, .Bang); ok {
|
||||
error_type = parse_error_type(parser)
|
||||
}
|
||||
if current(parser).kind == .Newline {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if current(parser).kind != .Left_Brace {
|
||||
delete(params, parser.module.allocator)
|
||||
return invalid_expr(parser, start.span, "expected function literal body")
|
||||
}
|
||||
body := parse_block(parser)
|
||||
end := previous(parser)
|
||||
function_id := ast.function_id(len(parser.module.functions))
|
||||
append(&parser.module.functions, ast.Function{
|
||||
span=span_from(start.span, end.span),
|
||||
name=symbol.INVALID,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=false,
|
||||
generated=true,
|
||||
has_body=true,
|
||||
variadic=variadic,
|
||||
params=params,
|
||||
result=result,
|
||||
error=error_type,
|
||||
body=body,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Function_Literal,
|
||||
span=span_from(start.span, end.span),
|
||||
integer=u64(function_id),
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
parse_record_field_type :: proc(parser: ^Parser, allow_anonymous_struct_payload: bool) -> types.Type {
|
||||
if allow_anonymous_struct_payload && current(parser).kind == .Keyword_Struct {
|
||||
return parse_inline_struct_payload_type(parser)
|
||||
|
||||
+46
-5
@@ -2913,19 +2913,21 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(test)
|
||||
field_function_pointer_calls_lower_as_indirect_calls :: proc(t: ^testing.T) {
|
||||
function_literals_lower_as_function_pointer_values :: proc(t: ^testing.T) {
|
||||
text := `Callbacks :: struct {
|
||||
call @func(value i32) i32
|
||||
value i32
|
||||
}
|
||||
plus_one func(value i32) i32 {
|
||||
return value + 1
|
||||
}
|
||||
run func(callbacks Callbacks) i32 {
|
||||
return callbacks.call(callbacks.value)
|
||||
}
|
||||
main func() i32 {
|
||||
callbacks Callbacks = Callbacks { call = plus_one, value = 41 }
|
||||
callbacks Callbacks = Callbacks {
|
||||
call = func(value i32) i32 {
|
||||
return value + 1
|
||||
},
|
||||
value = 41,
|
||||
}
|
||||
return run(callbacks) - 42
|
||||
}
|
||||
`
|
||||
@@ -2943,6 +2945,14 @@ main func() i32 {
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
|
||||
generated_functions := 0
|
||||
literal_exprs := 0
|
||||
for function in ast_module.functions {
|
||||
generated_functions += 1 if function.generated else 0
|
||||
}
|
||||
for expr in ast_module.exprs {
|
||||
literal_exprs += 1 if expr.kind == .Function_Literal else 0
|
||||
}
|
||||
indirect_calls := 0
|
||||
for expr in hir_module.exprs {
|
||||
if expr.kind == .Call && expr.left != hir.INVALID_EXPR {
|
||||
@@ -2950,10 +2960,41 @@ main func() i32 {
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, generated_functions, 1)
|
||||
testing.expect_value(t, literal_exprs, 1)
|
||||
testing.expect(t, len(ir_module.functions) > 0)
|
||||
testing.expect(t, indirect_calls > 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
||||
text := `main func() i32 {
|
||||
offset i32 = 1
|
||||
callback @func(value i32) i32 = func(value i32) i32 {
|
||||
return value + offset
|
||||
}
|
||||
return callback(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "unresolved global 'offset'")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
field_function_pointer_calls_reject_non_callable_fields :: proc(t: ^testing.T) {
|
||||
text := `Box :: struct {
|
||||
|
||||
+4
-8
@@ -8,16 +8,12 @@ Allocator :: struct {
|
||||
|
||||
heap Allocator :: Allocator {
|
||||
context = none,
|
||||
alloc = heap_alloc,
|
||||
free = heap_free,
|
||||
}
|
||||
|
||||
heap_alloc func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
alloc = func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
return ptr_cast(u8, c.malloc(size))
|
||||
}
|
||||
|
||||
heap_free func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void {
|
||||
},
|
||||
free = func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void {
|
||||
c.free(memory)
|
||||
},
|
||||
}
|
||||
|
||||
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
||||
|
||||
Reference in New Issue
Block a user