From 1d2e55f53dfab5153dbfd0c03f28cd9daedb3b77 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 6 Apr 2025 19:20:07 -0400 Subject: Allow uninitialized variables when there's a sensible empty value (defaults to empty/zero value) --- examples/colorful/colorful.tm | 2 +- examples/commands/commands.tm | 4 +- examples/http-server/connection-queue.tm | 2 +- examples/http-server/http-server.tm | 4 +- examples/http/http.tm | 2 +- examples/ini/ini.tm | 4 +- examples/learnxiny.tm | 4 +- examples/log/log.tm | 2 +- examples/tomo-install/tomo-install.tm | 4 +- examples/tomodeps/tomodeps.tm | 10 +- examples/wrap/wrap.tm | 4 +- src/compile.c | 154 +++++++++++++++++++++++-------- src/parse.c | 16 ++-- src/typecheck.c | 5 +- test/arrays.tm | 6 +- test/defer.tm | 2 +- test/import.tm | 4 +- test/iterators.tm | 2 +- test/reductions.tm | 2 +- 19 files changed, 157 insertions(+), 76 deletions(-) diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm index d878b662..30f3fc12 100644 --- a/examples/colorful/colorful.tm +++ b/examples/colorful/colorful.tm @@ -141,7 +141,7 @@ struct _TermState( ) func apply(old,new:_TermState -> Text) - sequences : &[Text] = &[] + sequences : &[Text] _toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22") _toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23") _toggle(sequences, old.underline, new.underline, "4", "24") diff --git a/examples/commands/commands.tm b/examples/commands/commands.tm index 2dd5aaf7..81c43692 100644 --- a/examples/commands/commands.tm +++ b/examples/commands/commands.tm @@ -54,8 +54,8 @@ struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}) if input.length > 0 (&input_bytes).insert_all(input.bytes()) - stdout : [Byte] = [] - stderr : [Byte] = [] + stdout : [Byte] + stderr : [Byte] status := run_command(command.command, command.args, command.env, input_bytes, &stdout, &stderr) if inline C : Bool { WIFEXITED(_$status) } diff --git a/examples/http-server/connection-queue.tm b/examples/http-server/connection-queue.tm index 29759f38..c56069e1 100644 --- a/examples/http-server/connection-queue.tm +++ b/examples/http-server/connection-queue.tm @@ -12,7 +12,7 @@ struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(), func dequeue(queue:ConnectionQueue -> Int32) - conn : Int32? = none + conn : Int32? queue._mutex.lock() diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm index b814cdcb..92651ca1 100644 --- a/examples/http-server/http-server.tm +++ b/examples/http-server/http-server.tm @@ -17,7 +17,7 @@ use ./connection-queue.tm func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16) connections := ConnectionQueue() - workers : &[@pthread_t] = &[] + workers : &[@pthread_t] for i in num_threads workers.insert(pthread_t.new(func() repeat @@ -114,7 +114,7 @@ enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text)) return HTTPResponse("Found", 302, headers={"Location"=destination}) func load_routes(directory:Path -> {Text=RouteEntry}) - routes : &{Text=RouteEntry} = &{} + routes : &{Text=RouteEntry} for file in (directory ++ (./*)).glob() skip unless file.is_file() contents := file.read() or skip diff --git a/examples/http/http.tm b/examples/http/http.tm index 39cbfd6d..29727e6e 100644 --- a/examples/http/http.tm +++ b/examples/http/http.tm @@ -8,7 +8,7 @@ struct HTTPResponse(code:Int, body:Text) enum _Method(GET, POST, PUT, PATCH, DELETE) func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse) - chunks : @[Text] = @[] + chunks : @[Text] save_chunk := func(chunk:CString, size:Int64, n:Int64) chunks.insert(inline C:Text { Text$format("%.*s", _$size*_$n, _$chunk) diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm index 7d53d7e5..c24cb4b9 100644 --- a/examples/ini/ini.tm +++ b/examples/ini/ini.tm @@ -11,8 +11,8 @@ _HELP := " func parse_ini(path:Path -> {Text={Text=Text}}) text := path.read() or exit("Could not read INI file: $\[31;1]$(path)$\[]") - sections : @{Text=@{Text=Text}} = @{} - current_section : @{Text=Text} = @{} + sections : @{Text=@{Text=Text}} + current_section : @{Text=Text} # Line wraps: text = text.replace_pattern($Pat/\{1 nl}{0+space}/, " ") diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index 5bf8e69f..31d768eb 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -56,7 +56,7 @@ func main() my_numbers := [10, 20, 30] # Empty arrays require specifying the type: - empty_array : [Int] = [] + empty_array : [Int] >> empty_array.length = 0 @@ -120,7 +120,7 @@ func main() = 0 # Empty tables require specifying the key and value types: - empty_table : {Text=Int} = {} + empty_table : {Text=Int} # Tables can be iterated over either by key or key,value: for key in table diff --git a/examples/log/log.tm b/examples/log/log.tm index 3763303f..94984b81 100644 --- a/examples/log/log.tm +++ b/examples/log/log.tm @@ -3,7 +3,7 @@ use timestamp_format := CString("%F %T") -logfiles : @|Path| = @|| +logfiles : @|Path| func _timestamp(->Text) c_str := inline C:CString { diff --git a/examples/tomo-install/tomo-install.tm b/examples/tomo-install/tomo-install.tm index fd8b3c40..c7d752ee 100644 --- a/examples/tomo-install/tomo-install.tm +++ b/examples/tomo-install/tomo-install.tm @@ -11,7 +11,7 @@ _HELP := " " func find_urls(path:Path -> [Text]) - urls : @[Text] = @[] + urls : @[Text] if path.is_directory() for f in path.children() urls.insert_all(find_urls(f)) @@ -40,7 +40,7 @@ func main(paths:[Path]) say("Already installed: $url") skip - alias : Text? = none + alias : Text? curl_flags := ["-L"] if github := url_without_protocol.pattern_captures($Pat"github.com/{!/}/{!/}#{..}") user := github[1] diff --git a/examples/tomodeps/tomodeps.tm b/examples/tomodeps/tomodeps.tm index 1181f2be..1e6be615 100644 --- a/examples/tomodeps/tomodeps.tm +++ b/examples/tomodeps/tomodeps.tm @@ -16,7 +16,7 @@ func _get_file_dependencies(file:Path -> |Dependency|) say("Could not read file: $file") return || - deps : @|Dependency| = @|| + deps : @|Dependency| if lines := file.by_line() for line in lines if line.matches_pattern($Pat/use {..}.tm/) @@ -36,8 +36,8 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency=|Dependen _get_file_dependencies(path) is Module(module) dir := (~/.local/share/tomo/installed/$module) - module_deps : @|Dependency| = @|| - visited : @|Path| = @|| + module_deps : @|Dependency| + visited : @|Path| unvisited := @|f.resolved() for f in dir.files() if f.extension() == ".tm"| while unvisited.length > 0 file := unvisited.items[-1] @@ -58,7 +58,7 @@ func _build_dependency_graph(dep:Dependency, dependencies:@{Dependency=|Dependen _build_dependency_graph(dep2, dependencies) func get_dependency_graph(dep:Dependency -> {Dependency=|Dependency|}) - graph : @{Dependency=|Dependency|} = @{} + graph : @{Dependency=|Dependency|} _build_dependency_graph(dep, graph) return graph @@ -88,7 +88,7 @@ func _draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|}, already_ _draw_tree(child, dependencies, already_printed, child_prefix, is_child_last) func draw_tree(dep:Dependency, dependencies:{Dependency=|Dependency|}) - printed : @|Dependency| = @|| + printed : @|Dependency| say(_printable_name(dep)) printed.add(dep) deps := dependencies[dep] or || diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm index 303393ee..1a29701f 100644 --- a/examples/wrap/wrap.tm +++ b/examples/wrap/wrap.tm @@ -33,7 +33,7 @@ func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text) ... and I can't split it without splitting into chunks smaller than $min_split. ") - lines : @[Text] = @[] + lines : @[Text] line := "" for word in text.split($/{whitespace}/) letters := word.split() @@ -93,7 +93,7 @@ func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UN (/dev/stdout) first := yes - wrapped_paragraphs : @[Text] = @[] + wrapped_paragraphs : @[Text] for paragraph in text.split($/{2+ nl}/) wrapped_paragraphs.insert( wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen) diff --git a/src/compile.c b/src/compile.c index 1df56f3e..0b7f19f5 100644 --- a/src/compile.c +++ b/src/compile.c @@ -31,6 +31,8 @@ static CORD compile_int_to_type(env_t *env, ast_t *ast, type_t *target); static CORD compile_unsigned_type(type_t *t); static CORD promote_to_optional(type_t *t, CORD code); static CORD compile_none(type_t *t); +static CORD compile_empty(type_t *t); +static CORD compile_declared_value(env_t *env, ast_t *declaration_ast); static CORD compile_to_type(env_t *env, ast_t *ast, type_t *t); static CORD compile_typed_array(env_t *env, ast_t *ast, type_t *array_type); static CORD compile_typed_set(env_t *env, ast_t *ast, type_t *set_type); @@ -214,7 +216,8 @@ static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t break; } case Declare: { - add_closed_vars(closed_vars, enclosing_scope, env, Match(ast, Declare)->value); + ast_t *value = Match(ast, Declare)->value; + add_closed_vars(closed_vars, enclosing_scope, env, value); bind_statement(env, ast); break; } @@ -334,6 +337,8 @@ static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t if (condition->tag == Declare) { env_t *truthy_scope = fresh_scope(env); bind_statement(truthy_scope, condition); + if (!Match(condition, Declare)->value) + code_err(condition, "This declared variable must have an initial value"); add_closed_vars(closed_vars, enclosing_scope, env, Match(condition, Declare)->value); ast_t *var = Match(condition, Declare)->var; type_t *cond_t = get_type(truthy_scope, var); @@ -1132,17 +1137,9 @@ static CORD _compile_statement(env_t *env, ast_t *ast) CORD test_code; if (test->expr->tag == Declare) { auto decl = Match(test->expr, Declare); - const char *varname = Match(decl->var, Var)->name; - if (streq(varname, "_")) - return compile_statement(env, WrapAST(ast, DocTest, .expr=decl->value, .expected=test->expected, .skip_source=test->skip_source)); - CORD var = CORD_all("_$", Match(decl->var, Var)->name); type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (!t) code_err(decl->value, "I couldn't figure out the type of this value!"); - CORD val_code = compile_maybe_incref(env, decl->value, t); - if (t->tag == FunctionType) { - assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); - t = Type(ClosureType, t); - } + CORD var = CORD_all("_$", Match(decl->var, Var)->name); + CORD val_code = compile_declared_value(env, test->expr); setup = CORD_all(compile_declaration(t, var), ";\n"); test_code = CORD_all("(", var, " = ", val_code, ")"); expr_t = t; @@ -1229,17 +1226,16 @@ static CORD _compile_statement(env_t *env, ast_t *ast) auto decl = Match(ast, Declare); const char *name = Match(decl->var, Var)->name; if (streq(name, "_")) { // Explicit discard - return CORD_all("(void)", compile(env, decl->value), ";"); + if (decl->value) + return CORD_all("(void)", compile(env, decl->value), ";"); + else + return CORD_EMPTY; } else { type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value"); - CORD val_code = compile_maybe_incref(env, decl->value, t); - if (t->tag == FunctionType) { - assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); - t = Type(ClosureType, t); - } + CORD val_code = compile_declared_value(env, ast); return CORD_all(compile_declaration(t, CORD_cat("_$", name)), " = ", val_code, ";"); } } @@ -1792,6 +1788,8 @@ static CORD _compile_statement(env_t *env, ast_t *ast) auto if_ = Match(ast, If); ast_t *condition = if_->condition; if (condition->tag == Declare) { + if (Match(condition, Declare)->value == NULL) + code_err(condition, "This declaration must have a value"); env_t *truthy_scope = fresh_scope(env); CORD code = CORD_all("IF_DECLARE(", compile_statement(truthy_scope, condition), ", "); bind_statement(truthy_scope, condition); @@ -2474,6 +2472,96 @@ CORD compile_none(type_t *t) } } +CORD compile_empty(type_t *t) +{ + if (t == NULL) + compiler_err(NULL, NULL, NULL, "I can't compile a value with no type"); + + if (t->tag == OptionalType) + return compile_none(t); + + if (t == PATH_TYPE) return "NONE_PATH"; + else if (t == PATH_TYPE_TYPE) return "((OptionalPathType_t){})"; + + switch (t->tag) { + case BigIntType: return "I(0)"; + case IntType: { + switch (Match(t, IntType)->bits) { + case TYPE_IBITS8: return "Int8(0)"; + case TYPE_IBITS16: return "Int16(0)"; + case TYPE_IBITS32: return "Int32(0)"; + case TYPE_IBITS64: return "Int64(0)"; + default: errx(1, "Invalid integer bit size"); + } + break; + } + case ByteType: return "((Byte_t)0)"; + case BoolType: return "((Bool_t)no)"; + case ArrayType: return "((Array_t){})"; + case TableType: case SetType: return "((Table_t){})"; + case TextType: return "Text(\"\")"; + case CStringType: return "\"\""; + case PointerType: { + auto ptr = Match(t, PointerType); + CORD empty_pointed = compile_empty(ptr->pointed); + return empty_pointed == CORD_EMPTY ? CORD_EMPTY : CORD_all(ptr->is_stack ? "stack(" : "heap(", empty_pointed, ")"); + } + case NumType: { + return Match(t, NumType)->bits == TYPE_NBITS32 ? "Num32(0.0f)" : "Num64(0.0)" ; + } + case StructType: { + auto struct_ = Match(t, StructType); + CORD code = CORD_all("((", compile_type(t), "){"); + for (arg_t *field = struct_->fields; field; field = field->next) { + CORD empty_field = field->default_val + ? compile(struct_->env, field->default_val) + : compile_empty(field->type); + if (empty_field == CORD_EMPTY) + return CORD_EMPTY; + + code = CORD_all(code, empty_field); + if (field->next) + code = CORD_all(code, ", "); + } + return CORD_all(code, "})"); + } + case EnumType: { + auto enum_ = Match(t, EnumType); + tag_t *tag = enum_->tags; + assert(tag); + assert(tag->type); + if (Match(tag->type, StructType)->fields) + return CORD_all("((", compile_type(t), "){.$tag=", String(tag->tag_value), ", .", tag->name, "=", compile_empty(tag->type), "})"); + else + return CORD_all("((", compile_type(t), "){.$tag=", String(tag->tag_value), "})"); + } + default: return CORD_EMPTY; + } +} + +static CORD compile_declared_value(env_t *env, ast_t *declare_ast) +{ + auto decl = Match(declare_ast, Declare); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + + if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) + code_err(declare_ast, "You can't declare a variable with a ", type_to_str(t), " value"); + + if (decl->value) { + CORD val_code = compile_maybe_incref(env, decl->value, t); + if (t->tag == FunctionType) { + assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); + t = Type(ClosureType, t); + } + return val_code; + } else { + CORD val_code = compile_empty(t); + if (val_code == CORD_EMPTY) + code_err(declare_ast, "This type (", type_to_str(t), ") cannot be uninitialized. You must provide a value."); + return val_code; + } +} + ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) { auto e = Match(entry, TableEntry); @@ -3377,6 +3465,8 @@ CORD compile(env_t *env, ast_t *ast) CORD condition_code; if (condition->tag == Declare) { auto decl = Match(condition, Declare); + if (decl->value == NULL) + code_err(condition, "This declaration must have a value"); type_t *condition_type = decl->type ? parse_type_ast(env, decl->type) : get_type(env, Match(condition, Declare)->value); @@ -4121,17 +4211,9 @@ CORD compile_top_level_code(env_t *env, ast_t *ast) const char *decl_name = Match(decl->var, Var)->name; CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name); type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); - if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) - code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value"); - - CORD val_code = compile_maybe_incref(env, decl->value, t); - if (t->tag == FunctionType) { - assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); - t = Type(ClosureType, t); - } - + CORD val_code = compile_declared_value(env, ast); bool is_private = decl_name[0] == '_'; - if (is_constant(env, decl->value)) { + if ((decl->value && is_constant(env, decl->value)) || (!decl->value && !has_heap_memory(t))) { set_binding(env, decl_name, t, full_name); return CORD_all( is_private ? "static " : CORD_EMPTY, @@ -4215,17 +4297,9 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) auto decl = Match(stmt->ast, Declare); const char *decl_name = Match(decl->var, Var)->name; CORD full_name = CORD_all(namespace_prefix(env, env->namespace), decl_name); - type_t *t = get_type(env, decl->value); - if (t->tag == AbortType || t->tag == VoidType || t->tag == ReturnType) - code_err(stmt->ast, "You can't declare a variable with a ", type_to_str(t), " value"); - - CORD val_code = compile_maybe_incref(env, decl->value, t); - if (t->tag == FunctionType) { - assert(promote(env, decl->value, &val_code, t, Type(ClosureType, t))); - t = Type(ClosureType, t); - } - - if (!is_constant(env, decl->value)) { + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); + CORD val_code = compile_declared_value(env, stmt->ast); + if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { env->code->variable_initializers = CORD_all( env->code->variable_initializers, with_source_info( @@ -4406,7 +4480,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a if (is_private) return CORD_EMPTY; - type_t *t = get_type(env, decl->value); + type_t *t = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); if (t->tag == FunctionType) t = Type(ClosureType, t); assert(t->tag != ModuleType); @@ -4414,7 +4488,7 @@ CORD compile_statement_namespace_header(env_t *env, Path_t header_path, ast_t *a code_err(ast, "You can't declare a variable with a ", type_to_str(t), " value"); return CORD_all( - compile_statement_type_header(env, header_path, decl->value), + decl->value ? compile_statement_type_header(env, header_path, decl->value) : CORD_EMPTY, "extern ", compile_declaration(t, CORD_cat(namespace_prefix(env, env->namespace), decl_name)), ";\n"); } case FunctionDef: { diff --git a/src/parse.c b/src/parse.c index 7ba557fb..c76266ee 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1677,13 +1677,15 @@ PARSER(parse_declaration) { spaces(&pos); type_ast_t *type = optional(ctx, &pos, parse_type); spaces(&pos); - if (!match(&pos, "=")) return NULL; - ast_t *val = optional(ctx, &pos, parse_extended_expr); - if (!val) { - if (optional(ctx, &pos, parse_use)) - parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file"); - else - parser_err(ctx, pos, eol(pos), "This is not a valid expression"); + ast_t *val = NULL; + if (match(&pos, "=")) { + val = optional(ctx, &pos, parse_extended_expr); + if (!val) { + if (optional(ctx, &pos, parse_use)) + parser_err(ctx, start, pos, "'use' statements are only allowed at the top level of a file"); + else + parser_err(ctx, pos, eol(pos), "This is not a valid expression"); + } } return NewAST(ctx->file, start, pos, Declare, .var=var, .type=type, .value=val); } diff --git a/src/typecheck.c b/src/typecheck.c index fed02ec6..a869f73e 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -303,10 +303,11 @@ void bind_statement(env_t *env, ast_t *statement) return; if (get_binding(env, name)) code_err(decl->var, "A ", type_to_str(get_binding(env, name)->type), " called ", quoted(name), " has already been defined"); - bind_statement(env, decl->value); + if (decl->value) + bind_statement(env, decl->value); type_t *type = decl->type ? parse_type_ast(env, decl->type) : get_type(env, decl->value); if (!type) - code_err(decl->value, "I couldn't figure out the type of this value"); + code_err(statement, "I couldn't figure out the type of this value"); if (type->tag == FunctionType) type = Type(ClosureType, type); CORD prefix = namespace_prefix(env, env->namespace); diff --git a/test/arrays.tm b/test/arrays.tm index f2ed2c7a..8122106c 100644 --- a/test/arrays.tm +++ b/test/arrays.tm @@ -3,6 +3,10 @@ func main() >> nums : [Num32] = [] = [] + do + >> nums : [Num32] + = [] + do >> arr := [10, 20, 30] = [10, 20, 30] @@ -104,7 +108,7 @@ func main() >> heap := @[(i * 1337) mod 37 for i in 10] >> heap.heapify() >> heap - heap_order : @[Int] = @[] + heap_order : @[Int] repeat heap_order.insert(heap.heap_pop() or stop) >> heap_order[] == heap_order.sorted() diff --git a/test/defer.tm b/test/defer.tm index e5033075..6c204851 100644 --- a/test/defer.tm +++ b/test/defer.tm @@ -1,6 +1,6 @@ func main() x := 123 - nums : @[Int] = @[] + nums : @[Int] do defer nums.insert(x) diff --git a/test/import.tm b/test/import.tm index a6a6fa16..0686190b 100644 --- a/test/import.tm +++ b/test/import.tm @@ -8,11 +8,11 @@ func returns_imported_type(->ImportedType) return get_value() # Imported from ./use_import.tm func main() - >> empty : [vectors.Vec2] = [] + >> empty : [vectors.Vec2] >> returns_vec() = Vec2(x=1, y=2) - >> imported : [ImportedType] = [] + >> imported : [ImportedType] >> returns_imported_type() = ImportedType("Hello") diff --git a/test/iterators.tm b/test/iterators.tm index 1816fd7a..08382cff 100644 --- a/test/iterators.tm +++ b/test/iterators.tm @@ -25,7 +25,7 @@ func main() = ["AB", "BC", "CD"] do - result : @[Text] = @[] + result : @[Text] for foo in pairwise(values) result.insert("$(foo.x)$(foo.y)") >> result[] diff --git a/test/reductions.tm b/test/reductions.tm index 2666e1a9..3ec2ef5e 100644 --- a/test/reductions.tm +++ b/test/reductions.tm @@ -4,7 +4,7 @@ func main() >> (+: [10, 20, 30]) = 60? - >> empty_ints : [Int] = [] + >> empty_ints : [Int] >> (+: empty_ints) = none -- cgit v1.2.3