From d4b10514fbe3afc7229efe74b015a664b52eba33 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 24 Nov 2024 16:36:27 -0500 Subject: [PATCH] Clean up some more null->none renames and fix the documentation. Also change the literal syntax to `NONE:T` instead of `!T` --- Makefile | 5 ++++- ast.c | 4 ++-- ast.h | 4 ++-- compile.c | 14 ++++++------- docs/optionals.md | 44 +++++++++++++++++++-------------------- environment.c | 36 ++++++++++++++++---------------- examples/base64/base64.tm | 4 ++-- examples/http/http.tm | 4 ++-- parse.c | 17 ++++++++------- repl.c | 2 +- test/iterators.tm | 4 ++-- test/optionals.tm | 38 ++++++++++++++++----------------- test/structs.tm | 4 ++-- typecheck.c | 8 +++---- 14 files changed, 97 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index 9fdc058..c8515b9 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,9 @@ clean: %: %.md pandoc --lua-filter=.pandoc/bold-code.lua -s $< -t man -o $@ +examples: + tomo -IL examples/vectors examples/base64 examples/log examples/ini examples/game examples/http examples/threads examples/tomodeps examples/tomo-install examples/wrap + install: tomo libtomo.so tomo.1 mkdir -p -m 755 "$(PREFIX)/man/man1" "$(PREFIX)/bin" "$(PREFIX)/include/tomo" "$(PREFIX)/lib" "$(PREFIX)/share/tomo/modules" cp -v stdlib/*.h "$(PREFIX)/include/tomo/" @@ -81,4 +84,4 @@ uninstall: rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/include/tomo" "$(PREFIX)/lib/libtomo.so" "$(PREFIX)/share/tomo"; \ .SUFFIXES: -.PHONY: all clean install uninstall test tags +.PHONY: all clean install uninstall test tags examples diff --git a/ast.c b/ast.c index 8626545..750245f 100644 --- a/ast.c +++ b/ast.c @@ -101,7 +101,7 @@ CORD ast_to_xml(ast_t *ast) switch (ast->tag) { #define T(type, ...) case type: { auto data = ast->__data.type; (void)data; return CORD_asprintf(__VA_ARGS__); } T(Unknown, "") - T(Null, "%r", type_ast_to_xml(data.type)) + T(None, "%r", type_ast_to_xml(data.type)) T(Bool, "", data.b ? "yes" : "no") T(Var, "%s", data.name) T(Int, "%s", data.str) @@ -203,7 +203,7 @@ int printf_ast(FILE *stream, const struct printf_info *info, const void *const a PUREFUNC bool is_idempotent(ast_t *ast) { switch (ast->tag) { - case Int: case Bool: case Num: case Var: case Null: case TextLiteral: return true; + case Int: case Bool: case Num: case Var: case None: case TextLiteral: return true; case Index: { auto index = Match(ast, Index); return is_idempotent(index->indexed) && index->index != NULL && is_idempotent(index->index); diff --git a/ast.h b/ast.h index 3535734..01ca953 100644 --- a/ast.h +++ b/ast.h @@ -122,7 +122,7 @@ struct type_ast_s { typedef enum { Unknown = 0, - Null, Bool, Var, + None, Bool, Var, Int, Num, TextLiteral, TextJoin, PrintStatement, Declare, Assign, @@ -155,7 +155,7 @@ struct ast_s { struct {} Unknown; struct { type_ast_t *type; - } Null; + } None; struct { bool b; } Bool; diff --git a/compile.c b/compile.c index 46ee822..817b322 100644 --- a/compile.c +++ b/compile.c @@ -1557,7 +1557,7 @@ CORD compile_to_type(env_t *env, ast_t *ast, type_t *t) { if (ast->tag == Int && is_numeric_type(t)) return compile_int_to_type(env, ast, t); - if (ast->tag == Null && Match(ast, Null)->type == NULL) + if (ast->tag == None && Match(ast, None)->type == NULL) return compile_null(t); CORD code = compile(env, ast); type_t *actual = get_type(env, ast); @@ -1890,7 +1890,7 @@ CORD compile_null(type_t *t) env_t *enum_env = Match(t, EnumType)->env; return CORD_all("((", compile_type(t), "){", namespace_prefix(enum_env, enum_env->namespace), "null})"); } - default: compiler_err(NULL, NULL, NULL, "Null isn't implemented for this type: %T", t); + default: compiler_err(NULL, NULL, NULL, "NONE isn't implemented for this type: %T", t); } } @@ -1930,10 +1930,10 @@ static CORD compile_num_to_type(ast_t *ast, type_t *type) CORD compile(env_t *env, ast_t *ast) { switch (ast->tag) { - case Null: { - if (!Match(ast, Null)->type) - code_err(ast, "This 'NULL' needs to specify what type it is using `!Type` syntax"); - type_t *t = parse_type_ast(env, Match(ast, Null)->type); + case None: { + if (!Match(ast, None)->type) + code_err(ast, "This 'NONE' needs to specify what type it is using `NONE:Type` syntax"); + type_t *t = parse_type_ast(env, Match(ast, None)->type); return compile_null(t); } case Bool: return Match(ast, Bool)->b ? "yes" : "no"; @@ -2746,7 +2746,7 @@ CORD compile(env_t *env, ast_t *ast) self = compile_to_pointer_depth(env, call->self, 0, false); arg_t *arg_spec = new(arg_t, .name="count", .type=INT_TYPE, .next=new(arg_t, .name="weights", .type=Type(ArrayType, .item_type=Type(NumType)), - .default_val=FakeAST(Null, .type=new(type_ast_t, .tag=ArrayTypeAST, + .default_val=FakeAST(None, .type=new(type_ast_t, .tag=ArrayTypeAST, .__data.ArrayTypeAST.item=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Num"))), .next=new(arg_t, .name="rng", .type=RNG_TYPE, .default_val=default_rng))); return CORD_all("Array$sample(", self, ", ", compile_arguments(env, ast, arg_spec, call->args), ", ", diff --git a/docs/optionals.md b/docs/optionals.md index 584625f..a0f5462 100644 --- a/docs/optionals.md +++ b/docs/optionals.md @@ -35,36 +35,36 @@ Optional types are written using a `?` after the type name. So, an optional integer would be written as `Int?` and an optional array of texts would be written as `[Text]?`. -Null values can be written explicitly using the `!` prefix operator and the -type of null value. For example, if you wanted to declare a variable that could -be either an integer value or a null value and initialize it as a null value, -you would write it as: +None can be written explicitly using `NONE` with a type annotation. For +example, if you wanted to declare a variable that could be either an integer +value or `NONE` and initialize it as none, you would write it as: ```tomo -x := !Int +x := NONE:Int ``` Similarly, if you wanted to declare a variable that could be an array of texts -or null and initialize it as null, you would write: +or none and initialize it as none, you would write: ```tomo x := ![Text] ``` -If you want to declare a variable and initialize it with a non-null value, but -keep open the possibility of assigning a null value later, you can use the -postfix `?` operator to indicate that a value is optional: +If you want to declare a variable and initialize it with a non-none value, but +keep open the possibility of assigning `NONE` later, you can use the postfix +`?` operator to indicate that a value is optional: ```tomo x := 5? -# Later on, assign null: +# Later on, assign none: x = !Int ``` ## Type Inference -For convenience, null values can also be written as `NONE` for any type in -situations where the compiler knows what type of optional value is expected: +For convenience, `NONE` can also be written without the explicit type +annotation for any type in situations where the compiler knows what type of +optional value is expected: - When assigning to a variable that has already been declared as optional. - When returning from a function with an explicit optional return type. @@ -82,7 +82,7 @@ func doop(arg:Int?)->Text?: doop(NONE) ``` -Non-null values can also be automatically promoted to optional values without +Non-none values can also be automatically promoted to optional values without the need for an explicit `?` operator in the cases listed above: ```tomo @@ -95,12 +95,12 @@ func doop(arg:Int?)->Text?: doop(123) ``` -## Null Checking +## None Checking -In addition to using conditionals to check for null values, you can also use -`or` to get a non-null value by either providing an alternative non-null value -or by providing an early out statement like `return`/`skip`/`stop` or a function -with an `Abort` type like `fail()` or `exit()`: +In addition to using conditionals to check for `NONE`, you can also use `or` to +get a non-none value by either providing an alternative non-none value or by +providing an early out statement like `return`/`skip`/`stop` or a function with +an `Abort` type like `fail()` or `exit()`: ```tomo maybe_x := 5? @@ -120,7 +120,7 @@ func do_stuff(matches:[Text]): for line in lines: matches := line:matches($/{..},{..}/) or skip - # The `or skip` above means that if we're here, `matches` is non-null: + # The `or skip` above means that if we're here, `matches` is non-none: do_stuff(matches) ``` @@ -132,9 +132,9 @@ booleans, texts, enums, nums, or integers (`Int` type only). This is done by using carefully chosen values, such as `0` for pointers, `2` for booleans, or a negative length for arrays. However, for fixed-size integers (`Int64`, `Int32`, `Int16`, and `Int8`), bytes, and structs, an additional byte is required for -out-of-band information about whether the value is null or not. +out-of-band information about whether the value is none or not. -Floating point numbers (`Num` and `Num32`) use `NaN` to represent null, so -optional nums should be careful to avoid using `NaN` as a non-null value. This +Floating point numbers (`Num` and `Num32`) use `NaN` to represent none, so +optional nums should be careful to avoid using `NaN` as a non-none value. This option was chosen to minimize the memory overhead of optional nums and because `NaN` literally means "not a number". diff --git a/environment.c b/environment.c index 8be31dc..6f2ff88 100644 --- a/environment.c +++ b/environment.c @@ -47,7 +47,7 @@ env_t *new_compilation_unit(CORD libname) {"exit", {.code="tomo_exit", .type=Type(FunctionType, .args=new( arg_t, .name="message", .type=Type(OptionalType, .type=Type(TextType)), - .default_val=FakeAST(Null, .type=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Text")), + .default_val=FakeAST(None, .type=new(type_ast_t, .tag=VarTypeAST, .__data.VarTypeAST.name="Text")), .next=new(arg_t, .name="code", .type=Type(IntType, .bits=TYPE_IBITS32), .default_val=FakeAST(InlineCCode, .code="1", .type=Type(IntType, .bits=TYPE_IBITS32)))), .ret=Type(AbortType))}}, @@ -295,29 +295,29 @@ env_t *new_compilation_unit(CORD libname) // Used as a default for functions below: {"now", "Moment$now", "func(->Moment)"}, - {"after", "Moment$after", "func(moment:Moment,seconds,minutes,hours=0.0,days,weeks,months,years=0,timezone=!Text -> Moment)"}, - {"date", "Moment$date", "func(moment:Moment,timezone=!Text -> Text)"}, - {"day_of_month", "Moment$day_of_month", "func(moment:Moment,timezone=!Text -> Int)"}, - {"day_of_week", "Moment$day_of_week", "func(moment:Moment,timezone=!Text -> Int)"}, - {"day_of_year", "Moment$day_of_year", "func(moment:Moment,timezone=!Text -> Int)"}, - {"format", "Moment$format", "func(moment:Moment,format=\"%Y-%m-%dT%H:%M:%S%z\",timezone=!Text -> Text)"}, + {"after", "Moment$after", "func(moment:Moment,seconds,minutes,hours=0.0,days,weeks,months,years=0,timezone=NONE:Text -> Moment)"}, + {"date", "Moment$date", "func(moment:Moment,timezone=NONE:Text -> Text)"}, + {"day_of_month", "Moment$day_of_month", "func(moment:Moment,timezone=NONE:Text -> Int)"}, + {"day_of_week", "Moment$day_of_week", "func(moment:Moment,timezone=NONE:Text -> Int)"}, + {"day_of_year", "Moment$day_of_year", "func(moment:Moment,timezone=NONE:Text -> Int)"}, + {"format", "Moment$format", "func(moment:Moment,format=\"%Y-%m-%dT%H:%M:%S%z\",timezone=NONE:Text -> Text)"}, {"from_unix_timestamp", "Moment$from_unix_timestamp", "func(timestamp:Int64 -> Moment)"}, {"get_local_timezone", "Moment$get_local_timezone", "func(->Text)"}, - {"hour", "Moment$hour", "func(moment:Moment,timezone=!Text -> Int)"}, + {"hour", "Moment$hour", "func(moment:Moment,timezone=NONE:Text -> Int)"}, {"hours_till", "Moment$hours_till", "func(now,then:Moment -> Num)"}, - {"minute", "Moment$minute", "func(moment:Moment,timezone=!Text -> Int)"}, + {"minute", "Moment$minute", "func(moment:Moment,timezone=NONE:Text -> Int)"}, {"minutes_till", "Moment$minutes_till", "func(now,then:Moment -> Num)"}, - {"month", "Moment$month", "func(moment:Moment,timezone=!Text -> Int)"}, - {"nanosecond", "Moment$nanosecond", "func(moment:Moment,timezone=!Text -> Int)"}, - {"new", "Moment$new", "func(year,month,day:Int,hour,minute=0,second=0.0,timezone=!Text -> Moment)"}, + {"month", "Moment$month", "func(moment:Moment,timezone=NONE:Text -> Int)"}, + {"nanosecond", "Moment$nanosecond", "func(moment:Moment,timezone=NONE:Text -> Int)"}, + {"new", "Moment$new", "func(year,month,day:Int,hour,minute=0,second=0.0,timezone=NONE:Text -> Moment)"}, {"parse", "Moment$parse", "func(text:Text, format=\"%Y-%m-%dT%H:%M:%S%z\" -> Moment?)"}, - {"relative", "Moment$relative", "func(moment:Moment,relative_to=Moment.now(),timezone=!Text -> Text)"}, - {"second", "Moment$second", "func(moment:Moment,timezone=!Text -> Int)"}, + {"relative", "Moment$relative", "func(moment:Moment,relative_to=Moment.now(),timezone=NONE:Text -> Text)"}, + {"second", "Moment$second", "func(moment:Moment,timezone=NONE:Text -> Int)"}, {"seconds_till", "Moment$seconds_till", "func(now:Moment,then:Moment -> Num)"}, - {"set_local_timezone", "Moment$set_local_timezone", "func(timezone=!Text)"}, - {"time", "Moment$time", "func(moment:Moment,seconds=no,am_pm=yes,timezone=!Text -> Text)"}, + {"set_local_timezone", "Moment$set_local_timezone", "func(timezone=NONE:Text)"}, + {"time", "Moment$time", "func(moment:Moment,seconds=no,am_pm=yes,timezone=NONE:Text -> Text)"}, {"unix_timestamp", "Moment$unix_timestamp", "func(moment:Moment -> Int64)"}, - {"year", "Moment$year", "func(moment:Moment,timezone=!Text -> Int)"}, + {"year", "Moment$year", "func(moment:Moment,timezone=NONE:Text -> Int)"}, )}, {"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t, {"append", "Path$append", "func(path:Path, text:Text, permissions=Int32(0o644))"}, @@ -340,7 +340,7 @@ env_t *new_compilation_unit(CORD libname) {"is_symlink", "Path$is_symlink", "func(path:Path -> Bool)"}, {"parent", "Path$parent", "func(path:Path -> Path)"}, {"read", "Path$read", "func(path:Path -> Text?)"}, - {"read_bytes", "Path$read_bytes", "func(path:Path, limit=!Int -> [Byte]?)"}, + {"read_bytes", "Path$read_bytes", "func(path:Path, limit=NONE:Int -> [Byte]?)"}, {"relative", "Path$relative", "func(path:Path, relative_to=(./) -> Path)"}, {"remove", "Path$remove", "func(path:Path, ignore_missing=no)"}, {"resolved", "Path$resolved", "func(path:Path, relative_to=(./) -> Path)"}, diff --git a/examples/base64/base64.tm b/examples/base64/base64.tm index f376262..bed94ac 100644 --- a/examples/base64/base64.tm +++ b/examples/base64/base64.tm @@ -59,10 +59,10 @@ lang Base64: output[dest+2] = _EQUAL_BYTE output[dest+3] = _EQUAL_BYTE - return Base64.without_escaping(Text.from_bytes(output) or return !Base64) + return Base64.without_escaping(Text.from_bytes(output) or return NONE) func decode_text(b64:Base64 -> Text?): - return Text.from_bytes(b64:decode_bytes() or return !Text) + return Text.from_bytes(b64:decode_bytes() or return NONE) func decode_bytes(b64:Base64 -> [Byte]?): bytes := b64.text_content:bytes() diff --git a/examples/http/http.tm b/examples/http/http.tm index f64a504..f36fac6 100644 --- a/examples/http/http.tm +++ b/examples/http/http.tm @@ -78,7 +78,7 @@ func _send(method:_Method, url:Text, data:Text?, headers=[:Text] -> HTTPResponse return HTTPResponse(Int(code), "":join(chunks)) func get(url:Text, headers=[:Text] -> HTTPResponse): - return _send(GET, url, !Text, headers) + return _send(GET, url, NONE, headers) func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse): return _send(POST, url, data, headers) @@ -89,7 +89,7 @@ func put(url:Text, data="", headers=["Content-Type: application/json", "Accept: func patch(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse): return _send(PATCH, url, data, headers) -func delete(url:Text, data=!Text, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse): +func delete(url:Text, data=NONE:Text, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse): return _send(DELETE, url, data, headers) func main(): diff --git a/parse.c b/parse.c index b1d9f73..d92df34 100644 --- a/parse.c +++ b/parse.c @@ -116,7 +116,7 @@ static PARSER(parse_lang_def); static PARSER(parse_namespace); static PARSER(parse_negative); static PARSER(parse_not); -static PARSER(parse_null); +static PARSER(parse_none); static PARSER(parse_num); static PARSER(parse_parens); static PARSER(parse_pass); @@ -1530,14 +1530,17 @@ PARSER(parse_lambda) { return NewAST(ctx->file, start, pos, Lambda, .id=ctx->next_lambda_id++, .args=args, .ret_type=ret, .body=body); } -PARSER(parse_null) { +PARSER(parse_none) { const char *start = pos; - if (match_word(&pos, "NONE")) - return NewAST(ctx->file, start, pos, Null, .type=NULL); - if (!match(&pos, "!")) return NULL; + if (!match_word(&pos, "NONE")) + return NULL; + + if (!match(&pos, ":")) + return NewAST(ctx->file, start, pos, None, .type=NULL); + type_ast_t *type = parse_type(ctx, pos); if (!type) return NULL; - return NewAST(ctx->file, start, type->end, Null, .type=type); + return NewAST(ctx->file, start, type->end, None, .type=type); } PARSER(parse_var) { @@ -1553,7 +1556,7 @@ PARSER(parse_term_no_suffix) { (void)( false || (term=parse_moment(ctx, pos)) // Must come before num/int - || (term=parse_null(ctx, pos)) + || (term=parse_none(ctx, pos)) || (term=parse_num(ctx, pos)) // Must come before int || (term=parse_int(ctx, pos)) || (term=parse_negative(ctx, pos)) // Must come after num/int diff --git a/repl.c b/repl.c index 4a5d664..7374ad3 100644 --- a/repl.c +++ b/repl.c @@ -337,7 +337,7 @@ void eval(env_t *env, ast_t *ast, void *dest) type_t *t = get_type(env, ast); size_t size = type_size(t); switch (ast->tag) { - case Null: + case None: if (dest) *(void**)dest = 0; break; case Bool: diff --git a/test/iterators.tm b/test/iterators.tm index 2adcab4..ebb040f 100644 --- a/test/iterators.tm +++ b/test/iterators.tm @@ -4,7 +4,7 @@ struct Pair(x:Text, y:Text) func pairwise(strs:[Text] -> func(->Pair?)): i := 1 return func(): - if i + 1 > strs.length: return !Pair + if i + 1 > strs.length: return NONE:Pair i += 1 return Pair(strs[i-1], strs[i])? @@ -12,7 +12,7 @@ func range(first:Int, last:Int -> func(->Int?)): i := first return func(): if i > last: - return !Int + return NONE:Int i += 1 return (i-1)? diff --git a/test/optionals.tm b/test/optionals.tm index 84a4a9c..acefdd2 100644 --- a/test/optionals.tm +++ b/test/optionals.tm @@ -11,74 +11,74 @@ enum Enum(X, Y(y:Int)): if should_i: return Enum.Y(123) else: - return !Enum + return NONE func maybe_int(should_i:Bool->Int?): if should_i: return 123 else: - return !Int + return NONE func maybe_int64(should_i:Bool->Int64?): if should_i: return Int64(123) else: - return !Int64 + return NONE func maybe_array(should_i:Bool->[Int]?): if should_i: return [10, 20, 30] else: - return ![Int] + return NONE func maybe_bool(should_i:Bool->Bool?): if should_i: return no else: - return !Bool + return NONE func maybe_text(should_i:Bool->Text?): if should_i: return "Hello" else: - return !Text + return NONE func maybe_num(should_i:Bool->Num?): if should_i: return 12.3 else: - return !Num + return NONE func maybe_lambda(should_i:Bool-> func()?): if should_i: return func(): say("hi!") else: - return !func() + return NONE func maybe_c_string(should_i:Bool->CString?): if should_i: return ("hi":as_c_string())? else: - return !CString + return NONE func maybe_channel(should_i:Bool->|Int|?): if should_i: return |:Int|? else: - return !|Int| + return NONE func maybe_thread(should_i:Bool->Thread?): if should_i: return Thread.new(func(): pass) else: - return !Thread + return NONE func main(): >> 5? = 5 : Int? >> if no: - !Int + NONE:Int else: 5 = 5 : Int? @@ -92,7 +92,7 @@ func main(): >> 5? or exit("Non-null is falsey") = 5 : Int - >> (!Int) or -1 + >> (NONE:Int) or -1 = -1 : Int do: @@ -279,13 +279,13 @@ func main(): = 123 : Int # Test comparisons, hashing, equality: - >> (!Int == 5?) + >> (NONE:Int == 5?) = no >> (5? == 5?) = yes - >> {!Int, !Int} + >> {NONE:Int, NONE:Int} = {NONE} - >> [5?, !Int, !Int, 6?]:sorted() + >> [5?, NONE:Int, NONE:Int, 6?]:sorted() = [NONE, NONE, 5, 6] do: @@ -296,7 +296,7 @@ func main(): = 5 do: - >> value := if var := !Int: + >> value := if var := NONE:Int: var else: 0 @@ -310,7 +310,7 @@ func main(): >> opt do: - >> opt := !Int + >> opt := NONE:Int >> if opt: >> opt else: @@ -319,7 +319,7 @@ func main(): >> not 5? = no - >> not !Int + >> not NONE:Int = yes >> [Struct(5,"A")?, Struct(6,"B"), Struct(7,"C")] diff --git a/test/structs.tm b/test/structs.tm index 26b24d6..ae5bec0 100644 --- a/test/structs.tm +++ b/test/structs.tm @@ -2,11 +2,11 @@ struct Single(x:Int) struct Pair(x,y:Int) struct Mixed(x:Int, text:Text) -struct LinkedList(x:Int, next=!@LinkedList) +struct LinkedList(x:Int, next=NONE:@LinkedList) struct Password(text:Text; secret) struct CorecursiveA(other:@CorecursiveB?) -struct CorecursiveB(other=!@CorecursiveA) +struct CorecursiveB(other=NONE:@CorecursiveA) func test_literals(): >> Single(123) diff --git a/typecheck.c b/typecheck.c index da81025..8bb5dc4 100644 --- a/typecheck.c +++ b/typecheck.c @@ -505,10 +505,10 @@ type_t *get_type(env_t *env, ast_t *ast) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" switch (ast->tag) { - case Null: { - if (!Match(ast, Null)->type) + case None: { + if (!Match(ast, None)->type) return Type(OptionalType, .type=NULL); - type_t *t = parse_type_ast(env, Match(ast, Null)->type); + type_t *t = parse_type_ast(env, Match(ast, None)->type); return Type(OptionalType, .type=t); } case Bool: { @@ -1310,7 +1310,7 @@ type_t *parse_type_string(env_t *env, const char *str) PUREFUNC bool is_constant(env_t *env, ast_t *ast) { switch (ast->tag) { - case Bool: case Num: case Null: return true; + case Bool: case Num: case None: return true; case Int: { auto info = Match(ast, Int); Int_t int_val = Int$parse(Text$from_str(info->str));