function literals (non-capturing)
This commit is contained in:
@@ -104,6 +104,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Call,
|
Call,
|
||||||
Try,
|
Try,
|
||||||
Catch,
|
Catch,
|
||||||
|
Function_Literal,
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr :: struct {
|
Expr :: struct {
|
||||||
@@ -209,6 +210,7 @@ Function :: struct {
|
|||||||
file: File_Id,
|
file: File_Id,
|
||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
|
generated: bool,
|
||||||
has_body: bool,
|
has_body: bool,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
params: []Param,
|
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) {
|
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 {
|
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)
|
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)
|
append(&stack, expr.right)
|
||||||
}
|
}
|
||||||
mark_block_imports_used(checker, expr.body, file)
|
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:
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Type, .Name:
|
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:
|
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
||||||
_ = pop(&stack)
|
_ = 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:
|
case .Name:
|
||||||
last = types.INVALID
|
last = types.INVALID
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
@@ -2421,7 +2443,7 @@ infer_statements :: proc(
|
|||||||
if statement.expr != ast.INVALID_EXPR {
|
if statement.expr != ast.INVALID_EXPR {
|
||||||
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
if is_runtime_type(checker, result_hint) {
|
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]
|
expr := checker.ast_module.exprs[statement.expr]
|
||||||
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
|
||||||
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
|
if local_index, ok := find_infer_local_index(locals^[:], expr.name); ok {
|
||||||
@@ -2433,7 +2455,7 @@ infer_statements :: proc(
|
|||||||
returned = result_hint
|
returned = result_hint
|
||||||
} else {
|
} else {
|
||||||
// `return G` for a global const: demand the result type onto it.
|
// `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
|
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
|
// 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).
|
// through bare names and numeric arithmetic. Calls remain a boundary (milestone 14.5).
|
||||||
record_demand :: proc(
|
record_demand :: proc(
|
||||||
@@ -4569,6 +4634,10 @@ build_expr :: proc(
|
|||||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||||
)
|
)
|
||||||
_ = pop(&stack)
|
_ = 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:
|
case .Type:
|
||||||
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
id := source.add(checker.diagnostics, expr.span, "type is not a runtime value")
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
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 {
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
||||||
return fmt.aprintf("main", allocator = checker.allocator)
|
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 !function.has_body && function.c_abi {
|
||||||
if len(function.link_name) > 0 {
|
if len(function.link_name) > 0 {
|
||||||
return strings.clone(function.link_name, checker.allocator)
|
return strings.clone(function.link_name, checker.allocator)
|
||||||
@@ -8423,8 +8495,11 @@ check :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
if function.generated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for previous in ast_module.functions[:index] {
|
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))
|
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,
|
right=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Keyword_Func:
|
||||||
|
return parse_function_literal(parser)
|
||||||
case .Left_Bracket:
|
case .Left_Bracket:
|
||||||
return parse_array_literal(parser, nesting)
|
return parse_array_literal(parser, nesting)
|
||||||
case .Dot:
|
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 {
|
parse_record_field_type :: proc(parser: ^Parser, allow_anonymous_struct_payload: bool) -> types.Type {
|
||||||
if allow_anonymous_struct_payload && current(parser).kind == .Keyword_Struct {
|
if allow_anonymous_struct_payload && current(parser).kind == .Keyword_Struct {
|
||||||
return parse_inline_struct_payload_type(parser)
|
return parse_inline_struct_payload_type(parser)
|
||||||
|
|||||||
+46
-5
@@ -2913,19 +2913,21 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(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 {
|
text := `Callbacks :: struct {
|
||||||
call @func(value i32) i32
|
call @func(value i32) i32
|
||||||
value i32
|
value i32
|
||||||
}
|
}
|
||||||
plus_one func(value i32) i32 {
|
|
||||||
return value + 1
|
|
||||||
}
|
|
||||||
run func(callbacks Callbacks) i32 {
|
run func(callbacks Callbacks) i32 {
|
||||||
return callbacks.call(callbacks.value)
|
return callbacks.call(callbacks.value)
|
||||||
}
|
}
|
||||||
main func() i32 {
|
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
|
return run(callbacks) - 42
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -2943,6 +2945,14 @@ main func() i32 {
|
|||||||
ir_module := lower.lower(&hir_module)
|
ir_module := lower.lower(&hir_module)
|
||||||
defer ir.destroy_module(&ir_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
|
indirect_calls := 0
|
||||||
for expr in hir_module.exprs {
|
for expr in hir_module.exprs {
|
||||||
if expr.kind == .Call && expr.left != hir.INVALID_EXPR {
|
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, 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, len(ir_module.functions) > 0)
|
||||||
testing.expect(t, indirect_calls > 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)
|
@(test)
|
||||||
field_function_pointer_calls_reject_non_callable_fields :: proc(t: ^testing.T) {
|
field_function_pointer_calls_reject_non_callable_fields :: proc(t: ^testing.T) {
|
||||||
text := `Box :: struct {
|
text := `Box :: struct {
|
||||||
|
|||||||
+4
-8
@@ -8,16 +8,12 @@ Allocator :: struct {
|
|||||||
|
|
||||||
heap Allocator :: Allocator {
|
heap Allocator :: Allocator {
|
||||||
context = none,
|
context = none,
|
||||||
alloc = heap_alloc,
|
alloc = func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||||
free = heap_free,
|
|
||||||
}
|
|
||||||
|
|
||||||
heap_alloc func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
|
||||||
return ptr_cast(u8, c.malloc(size))
|
return ptr_cast(u8, c.malloc(size))
|
||||||
}
|
},
|
||||||
|
free = func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void {
|
||||||
heap_free func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void {
|
|
||||||
c.free(memory)
|
c.free(memory)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
||||||
|
|||||||
Reference in New Issue
Block a user