aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compile.c59
-rw-r--r--docs/arrays.md8
-rw-r--r--docs/booleans.md6
-rw-r--r--docs/integers.md10
-rw-r--r--docs/iterators.md8
-rw-r--r--docs/moments.md4
-rw-r--r--docs/nums.md3
-rw-r--r--docs/optionals.md85
-rw-r--r--docs/paths.md8
-rw-r--r--docs/pointers.md2
-rw-r--r--docs/reductions.md4
-rw-r--r--docs/tables.md8
-rw-r--r--docs/text.md12
-rw-r--r--examples/learnxiny.tm4
-rw-r--r--parse.c2
-rw-r--r--stdlib/optionals.c8
-rw-r--r--test/arrays.tm8
-rw-r--r--test/optionals.tm56
-rw-r--r--test/paths.tm10
-rw-r--r--test/reductions.tm10
-rw-r--r--test/structs.tm2
-rw-r--r--test/tables.tm16
-rw-r--r--test/text.tm20
-rw-r--r--typecheck.c5
-rw-r--r--types.c10
25 files changed, 211 insertions, 157 deletions
diff --git a/compile.c b/compile.c
index d380eaf3..2d07c117 100644
--- a/compile.c
+++ b/compile.c
@@ -31,6 +31,7 @@ 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_null(type_t *t);
+static CORD compile_to_type(env_t *env, ast_t *ast, type_t *t);
CORD promote_to_optional(type_t *t, CORD code)
{
@@ -556,15 +557,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
// Common case: assigning to one variable:
type_t *lhs_t = get_type(env, assign->targets->ast);
env_t *val_scope = with_enum_scope(env, lhs_t);
- type_t *rhs_t = get_type(val_scope, assign->values->ast);
- CORD value;
- if (lhs_t->tag == IntType && assign->values->ast->tag == Int) {
- value = compile_int_to_type(val_scope, assign->values->ast, lhs_t);
- } else {
- value = compile_maybe_incref(val_scope, assign->values->ast);
- if (!promote(val_scope, &value, rhs_t, lhs_t))
- code_err(assign->values->ast, "You cannot assign a %T value to a %T operand", rhs_t, lhs_t);
- }
+ CORD value = compile_to_type(val_scope, assign->values->ast, lhs_t);
return CORD_asprintf(
"test((%r), %r, %r, %ld, %ld);",
compile_assignment(env, assign->targets->ast, value),
@@ -583,15 +576,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
for (ast_list_t *target = assign->targets, *value = assign->values; target && value; target = target->next, value = value->next) {
type_t *target_type = get_type(env, target->ast);
env_t *val_scope = with_enum_scope(env, target_type);
- type_t *value_type = get_type(val_scope, value->ast);
- CORD val_code;
- if (target_type->tag == IntType && value->ast->tag == Int) {
- val_code = compile_int_to_type(val_scope, value->ast, target_type);
- } else {
- val_code = compile_maybe_incref(val_scope, value->ast);
- if (!promote(val_scope, &val_code, value_type, target_type))
- code_err(value->ast, "You cannot assign a %T value to a %T operand", value_type, target_type);
- }
+ CORD val_code = compile_to_type(val_scope, value->ast, target_type);
CORD_appendf(&code, "%r $%ld = %r;\n", compile_type(target_type), i++, val_code);
}
i = 1;
@@ -655,15 +640,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
if (assign->targets && !assign->targets->next && is_idempotent(assign->targets->ast)) {
type_t *lhs_t = get_type(env, assign->targets->ast);
env_t *val_env = with_enum_scope(env, lhs_t);
- type_t *rhs_t = get_type(val_env, assign->values->ast);
- CORD val;
- if (lhs_t->tag == IntType && assign->values->ast->tag == Int) {
- val = compile_int_to_type(val_env, assign->values->ast, lhs_t);
- } else {
- val = compile_maybe_incref(val_env, assign->values->ast);
- if (!promote(val_env, &val, rhs_t, lhs_t))
- code_err(assign->values->ast, "You cannot assign a %T value to a %T operand", rhs_t, lhs_t);
- }
+ CORD val = compile_to_type(val_env, assign->values->ast, lhs_t);
return CORD_all(compile_assignment(env, assign->targets->ast, val), ";\n");
}
@@ -672,15 +649,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
for (ast_list_t *value = assign->values, *target = assign->targets; value && target; value = value->next, target = target->next) {
type_t *lhs_t = get_type(env, target->ast);
env_t *val_env = with_enum_scope(env, lhs_t);
- type_t *rhs_t = get_type(val_env, value->ast);
- CORD val;
- if (rhs_t->tag == IntType && value->ast->tag == Int) {
- val = compile_int_to_type(val_env, value->ast, rhs_t);
- } else {
- val = compile_maybe_incref(val_env, value->ast);
- if (!promote(val_env, &val, rhs_t, lhs_t))
- code_err(value->ast, "You cannot assign a %T value to a %T operand", rhs_t, lhs_t);
- }
+ CORD val = compile_to_type(val_env, value->ast, lhs_t);
CORD_appendf(&code, "%r $%ld = %r;\n", compile_type(lhs_t), i++, val);
}
i = 1;
@@ -1062,17 +1031,7 @@ CORD compile_statement(env_t *env, ast_t *ast)
code_err(ast, "This function is not supposed to return any values, according to its type signature");
env = with_enum_scope(env, env->fn_ctx->return_type);
- CORD value;
- if (env->fn_ctx->return_type->tag == IntType && ret->tag == Int) {
- value = compile_int_to_type(env, ret, env->fn_ctx->return_type);
- } else {
- type_t *ret_value_t = get_type(env, ret);
- value = compile(env, ret);
- if (!promote(env, &value, ret_value_t, env->fn_ctx->return_type))
- code_err(ast, "This function expects a return value of type %T, but this return has type %T",
- env->fn_ctx->return_type, ret_value_t);
- }
-
+ CORD value = compile_to_type(env, ret, env->fn_ctx->return_type);
if (env->deferred) {
code = CORD_all(compile_declaration(env->fn_ctx->return_type, "ret"), " = ", value, ";\n", code);
value = "ret";
@@ -1580,10 +1539,12 @@ CORD compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool
return val;
}
-static CORD compile_to_type(env_t *env, ast_t *ast, type_t *t)
+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)
+ return compile_null(t);
CORD code = compile(env, ast);
type_t *actual = get_type(env, ast);
if (!promote(env, &code, actual, t))
@@ -1956,6 +1917,8 @@ 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);
return compile_null(t);
}
diff --git a/docs/arrays.md b/docs/arrays.md
index 144bcd77..b69ae259 100644
--- a/docs/arrays.md
+++ b/docs/arrays.md
@@ -363,10 +363,10 @@ The index of the first occurrence or `!Int` if not found.
**Example:**
```tomo
>> [10, 20, 30, 40, 50]:find(20)
-= 2?
+= 2 : Int?
>> [10, 20, 30, 40, 50]:find(9999)
-= !Int
+= NULL : Int?
```
---
@@ -394,9 +394,9 @@ item matches.
**Example:**
```tomo
>> [4, 5, 6]:find(func(i:&Int): i:is_prime())
-= 5?
+= 5 : Int?
>> [4, 6, 8]:find(func(i:&Int): i:is_prime())
-= !Int
+= NULL : Int?
```
---
diff --git a/docs/booleans.md b/docs/booleans.md
index 80de6fa6..3537e3ee 100644
--- a/docs/booleans.md
+++ b/docs/booleans.md
@@ -29,9 +29,9 @@ func parse(text: Text -> Bool?)
**Example:**
```tomo
>> Bool.parse("yes")
-= yes?
+= yes : Bool?
>> Bool.parse("no")
-= no?
+= no : Bool?
>> Bool.parse("???")
-= !Bool
+= NULL : Bool?
```
diff --git a/docs/integers.md b/docs/integers.md
index f5e6838b..d995d139 100644
--- a/docs/integers.md
+++ b/docs/integers.md
@@ -171,22 +171,22 @@ func parse(text: Text -> Int?)
**Returns:**
The integer represented by the text. If the given text contains a value outside
of the representable range or if the entire text can't be parsed as an integer,
-a null value will be returned.
+`NULL` will be returned.
**Example:**
```tomo
>> Int.parse("123")
-= 123?
+= 123 : Int?
>> Int.parse("0xFF")
-= 255?
+= 255 : Int?
# Can't parse:
>> Int.parse("asdf")
-= !Int
+= NULL : Int?
# Outside valid range:
>> Int8.parse("9999999")
-= !Int
+= NULL : Int8?
```
---
diff --git a/docs/iterators.md b/docs/iterators.md
index 63a44fcf..ee777cfe 100644
--- a/docs/iterators.md
+++ b/docs/iterators.md
@@ -16,13 +16,13 @@ successively gets one line from a file at a time until the file is exhausted:
>> iter := (./test.txt):each_line()
>> iter()
-= "line one"?
+= "line one" : Text?
>> iter()
-= "line two"?
+= "line two" : Text?
>> iter()
-= "line three"?
+= "line three" : Text?
>> iter()
-= !Text
+= NULL : Text?
for line in (./test.txt):each_line():
pass
diff --git a/docs/moments.md b/docs/moments.md
index 58643eb6..6e2ac234 100644
--- a/docs/moments.md
+++ b/docs/moments.md
@@ -523,7 +523,7 @@ Returns a `Moment` object representing the current date and time.
**Description:**
Return a new `Moment` object parsed from the given string in the given format,
-or a null value if the value could not be successfully parsed.
+or `NULL` if the value could not be successfully parsed.
**Signature:**
```tomo
@@ -539,7 +539,7 @@ func parse(text: Text, format: Text = "%Y-%m-%dT%H:%M:%S%z" -> Moment?)
**Returns:**
If the text was successfully parsed according to the given format, return a
-`Moment` representing that information. Otherwise, return a null value.
+`Moment` representing that information. Otherwise, return `NULL`.
**Example:**
```tomo
diff --git a/docs/nums.md b/docs/nums.md
index c7171f37..0b55f71e 100644
--- a/docs/nums.md
+++ b/docs/nums.md
@@ -583,7 +583,8 @@ func parse(text: Text -> Num?)
- `text`: The text containing the number.
**Returns:**
-The number represented by the text or a null value if the entire text can't be parsed as a number.
+The number represented by the text or `NULL` if the entire text can't be parsed
+as a number.
**Example:**
```tomo
diff --git a/docs/optionals.md b/docs/optionals.md
index 2bc747f9..bb2aecfd 100644
--- a/docs/optionals.md
+++ b/docs/optionals.md
@@ -27,7 +27,75 @@ func maybe_takes_int(x:Int?):
This establishes a common language for talking about optional values without
having to use a more generalized form of `enum` which may have different naming
-conventions and which would generate a lot of unnecessary code.
+conventions and which would generate a lot of unnecessary code.
+
+## Syntax
+
+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:
+
+```tomo
+x := !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:
+
+```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:
+
+```tomo
+x := 5?
+# Later on, assign null:
+x = !Int
+```
+
+## Type Inference
+
+For convenience, null values can also be written as `NULL` 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.
+- When passing an argument to a function with an optional argument type.
+
+Here are some examples:
+
+```tomo
+x := 5?
+x = NULL
+
+func doop(arg:Int?)->Text?:
+ return NULL
+
+doop(NULL)
+```
+
+Non-null values can also be automatically promoted to optional values without
+the need for an explicit `?` operator in the cases listed above:
+
+```tomo
+x := !Int
+x = 5
+
+func doop(arg:Int?)->Text?:
+ return "okay"
+
+doop(123)
+```
+
+## Null 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
@@ -55,3 +123,18 @@ for line in lines:
# The `or skip` above means that if we're here, `matches` is non-null:
do_stuff(matches)
```
+
+## Implementation Notes
+
+The implementation of optional types is highly efficient and has no memory
+overhead for pointers, collection types (arrays, sets, tables, channels),
+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.
+
+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
+option was chosen to minimize the memory overhead of optional nums and because
+`NaN` literally means "not a number".
diff --git a/docs/paths.md b/docs/paths.md
index 86ec2f26..b2dfe384 100644
--- a/docs/paths.md
+++ b/docs/paths.md
@@ -492,10 +492,10 @@ raised.
**Example:**
```tomo
>> (./hello.txt):read()
-= "Hello"?
+= "Hello" : Text?
>> (./nosuchfile.xxx):read()
-= !Text
+= NULL : Text?
```
---
@@ -521,10 +521,10 @@ returned.
**Example:**
```tomo
>> (./hello.txt):read()
-= [72[B], 101[B], 108[B], 108[B], 111[B]]?
+= [72[B], 101[B], 108[B], 108[B], 111[B]] : [Byte]?
>> (./nosuchfile.xxx):read()
-= ![Byte]
+= NULL : [Byte]?
```
---
diff --git a/docs/pointers.md b/docs/pointers.md
index 174c4296..f1bd1b5a 100644
--- a/docs/pointers.md
+++ b/docs/pointers.md
@@ -113,7 +113,7 @@ inside of any datastructures as elements or members.
```tomo
nums := @[10, 20, 30]
>> nums:first(func(x:&Int): x / 2 == 10)
-= 2?
+= 2 : Int?
```
Normal `@` pointers can be promoted to immutable view pointers automatically,
diff --git a/docs/reductions.md b/docs/reductions.md
index b4e78624..2ed6879f 100644
--- a/docs/reductions.md
+++ b/docs/reductions.md
@@ -8,7 +8,7 @@ infix operator followed by a colon, followed by a collection:
nums := [10, 20, 30]
sum := (+: nums)
>> sum
-= 60?
+= 60 : Int?
```
Reductions return an optional value which will be a null value if the thing
@@ -22,7 +22,7 @@ nums := [:Int]
sum := (+: nums)
>> sum
-= !Int
+= NULL : Int?
>> sum or 0
= 0
diff --git a/docs/tables.md b/docs/tables.md
index 1de58118..6a6c7004 100644
--- a/docs/tables.md
+++ b/docs/tables.md
@@ -48,9 +48,9 @@ was found or error if it wasn't:
```tomo
>> t := {"x":1, "y":2}
>> t:get("x")
-= 1?
+= 1 : Int?
>> t:get("????")
-= !Int
+= NULL : Int?
>> t:get("x")!
= 1
```
@@ -187,10 +187,10 @@ The value associated with the key or null if the key is not found.
```tomo
>> t := {"A":1, "B":2}
>> t:get("A")
-= 1? : Int?
+= 1 : Int?
>> t:get("????")
-= !Int : Int?
+= NULL : Int?
>> t:get("A")!
= 1 : Int
diff --git a/docs/text.md b/docs/text.md
index 7557f05c..d433e902 100644
--- a/docs/text.md
+++ b/docs/text.md
@@ -693,13 +693,13 @@ containing information about the match.
**Example:**
```tomo
>> " #one #two #three ":find($/#{id}/, start=-999)
-= !Match
+= NULL : Match?
>> " #one #two #three ":find($/#{id}/, start=999)
-= !Match
+= NULL : Match?
>> " #one #two #three ":find($/#{id}/)
-= Match(text="#one", index=2, captures=["one"])?
+= Match(text="#one", index=2, captures=["one"]) : Match?
>> " #one #two #three ":find("{id}", start=6)
-= Match(text="#two", index=9, captures=["two"])?
+= Match(text="#two", index=9, captures=["two"]) : Match?
```
---
@@ -885,10 +885,10 @@ or a null value otherwise.
**Example:**
```tomo
>> "hello world":matches($/{id}/)
-= ![Text]
+= NULL : [Text]?
>> "hello world":matches($/{id} {id}/)
-= ["hello", "world"]?
+= ["hello", "world"] : [Text]?
```
---
diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm
index ed8e2596..99649129 100644
--- a/examples/learnxiny.tm
+++ b/examples/learnxiny.tm
@@ -106,13 +106,13 @@ func main():
# Tables are efficient hash maps
table := {"one": 1, "two": 2}
>> table:get("two")
- = 2?
+ = 2 : Int?
# The value returned is optional (because the key might not be in the table).
# Optional values can be converted to regular values using `!` (which will
# create a runtime error if the value is null) or the `or` operator:
>> table:get("two")!
- = 2
+ = 2 : Int
>> table:get("xxx") or 0
= 0
diff --git a/parse.c b/parse.c
index 8aebb1d3..bc25e89b 100644
--- a/parse.c
+++ b/parse.c
@@ -1532,6 +1532,8 @@ PARSER(parse_lambda) {
PARSER(parse_null) {
const char *start = pos;
+ if (match_word(&pos, "NULL"))
+ return NewAST(ctx->file, start, pos, Null, .type=NULL);
if (!match(&pos, "!")) return NULL;
type_ast_t *type = parse_type(ctx, pos);
if (!type) return NULL;
diff --git a/stdlib/optionals.c b/stdlib/optionals.c
index 34f14f4b..b6a5989e 100644
--- a/stdlib/optionals.c
+++ b/stdlib/optionals.c
@@ -64,18 +64,14 @@ public PUREFUNC bool is_null(const void *obj, const TypeInfo_t *non_optional_typ
}
}
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstack-protector"
public Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo_t *type)
{
if (!obj)
return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), Text("?"));
if (is_null(obj, type->OptionalInfo.type))
- return Text$concat(colorize ? Text("\x1b[31m!") : Text("!"), generic_as_text(NULL, false, type->OptionalInfo.type),
- colorize ? Text("\x1b[m") : Text(""));
- return Text$concat(generic_as_text(obj, colorize, type->OptionalInfo.type), colorize ? Text("\x1b[33m?\x1b[m") : Text("?"));
+ return colorize ? Text("\x1b[31mNULL\x1b[m") : Text("NULL");
+ return generic_as_text(obj, colorize, type->OptionalInfo.type);
}
-#pragma GCC diagnostic pop
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/test/arrays.tm b/test/arrays.tm
index 7fa1ef62..a6de26ce 100644
--- a/test/arrays.tm
+++ b/test/arrays.tm
@@ -164,11 +164,11 @@ func main():
= [1, 2, 3, 4, 5]
>> ["a", "b", "c"]:find("b")
- = 2?
+ = 2 : Int?
>> ["a", "b", "c"]:find("XXX")
- = !Int
+ = NULL : Int?
>> [10, 20]:first(func(i:&Int): i:is_prime())
- = !Int
+ = NULL : Int?
>> [4, 5, 6]:first(func(i:&Int): i:is_prime())
- = 2?
+ = 2 : Int?
diff --git a/test/optionals.tm b/test/optionals.tm
index eba1a685..8efae414 100644
--- a/test/optionals.tm
+++ b/test/optionals.tm
@@ -4,7 +4,7 @@ struct Struct(x:Int, y:Text):
if should_i:
return Struct(123, "hello")
else:
- return !Struct
+ return NULL
enum Enum(X, Y(y:Int)):
func maybe(should_i:Bool->Enum?):
@@ -75,13 +75,13 @@ func maybe_thread(should_i:Bool->Thread?):
func main():
>> 5?
- = 5? : Int?
+ = 5 : Int?
>> if no:
!Int
else:
5
- = 5? : Int?
+ = 5 : Int?
>> 5? or -1
= 5 : Int
@@ -98,9 +98,9 @@ func main():
do:
!! Ints:
>> yep := maybe_int(yes)
- = 123?
+ = 123 : Int?
>> nope := maybe_int(no)
- = !Int
+ = NULL : Int?
>> if yep:
>> yep
= 123
@@ -113,9 +113,9 @@ func main():
!! ...
!! Int64s:
>> yep := maybe_int64(yes)
- = 123?
+ = 123 : Int64?
>> nope := maybe_int64(no)
- = !Int64
+ = NULL : Int64?
>> if yep:
>> yep
= 123
@@ -128,9 +128,9 @@ func main():
!! ...
!! Arrays:
>> yep := maybe_array(yes)
- = [10, 20, 30]?
+ = [10, 20, 30] : [Int]?
>> nope := maybe_array(no)
- = ![Int]
+ = NULL : [Int]?
>> if yep:
>> yep
= [10, 20, 30]
@@ -143,9 +143,9 @@ func main():
!! ...
!! Bools:
>> yep := maybe_bool(yes)
- = no?
+ = no : Bool?
>> nope := maybe_bool(no)
- = !Bool
+ = NULL : Bool?
>> if yep:
>> yep
= no
@@ -158,9 +158,9 @@ func main():
!! ...
!! Text:
>> yep := maybe_text(yes)
- = "Hello"?
+ = "Hello" : Text?
>> nope := maybe_text(no)
- = !Text
+ = NULL : Text?
>> if yep:
>> yep
= "Hello"
@@ -173,9 +173,9 @@ func main():
!! ...
!! Nums:
>> yep := maybe_num(yes)
- = 12.3?
+ = 12.3 : Num?
>> nope := maybe_num(no)
- = !Num
+ = NULL : Num?
>> if yep:
>> yep
= 12.3
@@ -188,9 +188,9 @@ func main():
!! ...
!! Lambdas:
>> yep := maybe_lambda(yes)
- = func() [optionals.tm:54]?
+ = func() [optionals.tm:54] : func()?
>> nope := maybe_lambda(no)
- = !func()
+ = NULL : func()?
>> if yep:
>> yep
= func() [optionals.tm:54]
@@ -203,9 +203,9 @@ func main():
!! ...
!! Structs:
>> yep := Struct.maybe(yes)
- = Struct(x=123, y="hello")?
+ = Struct(x=123, y="hello") : Struct?
>> nope := Struct.maybe(no)
- = !Struct
+ = NULL : Struct?
>> if yep:
>> yep
= Struct(x=123, y="hello")
@@ -218,9 +218,9 @@ func main():
!! ...
!! Enums:
>> yep := Enum.maybe(yes)
- = Enum.Y(123)?
+ = Enum.Y(123) : Enum?
>> nope := Enum.maybe(no)
- = !Enum
+ = NULL : Enum?
>> if yep:
>> yep
= Enum.Y(123)
@@ -233,9 +233,9 @@ func main():
!! ...
!! C Strings:
>> yep := maybe_c_string(yes)
- = CString("hi")?
+ = CString("hi") : CString?
>> nope := maybe_c_string(no)
- = !CString
+ = NULL : CString?
>> if yep:
>> yep
= CString("hi")
@@ -250,7 +250,7 @@ func main():
>> yep := maybe_channel(yes)
# No "=" test here because channels use addresses in the text version
>> nope := maybe_channel(no)
- = !|:Int|
+ = NULL : |:Int|?
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
@@ -263,7 +263,7 @@ func main():
>> yep := maybe_thread(yes)
# No "=" test here because threads use addresses in the text version
>> nope := maybe_thread(no)
- = !Thread
+ = NULL : Thread?
>> if yep: >> yep
else: fail("Falsey: $yep")
>> if nope:
@@ -284,9 +284,9 @@ func main():
>> (5? == 5?)
= yes
>> {!Int, !Int}
- = {!Int}
+ = {NULL}
>> [5?, !Int, !Int, 6?]:sorted()
- = [!Int, !Int, 5?, 6?]
+ = [NULL, NULL, 5, 6]
do:
>> value := if var := 5?:
@@ -323,4 +323,4 @@ func main():
= yes
>> [Struct(5,"A")?, Struct(6,"B"), Struct(7,"C")]
- = [Struct(x=5, y="A")?, Struct(x=6, y="B")?, Struct(x=7, y="C")?]
+ = [Struct(x=5, y="A"), Struct(x=6, y="B"), Struct(x=7, y="C")]
diff --git a/test/paths.tm b/test/paths.tm
index 5e901453..62cecec6 100644
--- a/test/paths.tm
+++ b/test/paths.tm
@@ -23,9 +23,9 @@ func main():
>> tmpfile:write("Hello world")
>> tmpfile:append("!")
>> tmpfile:read()
- = "Hello world!"?
+ = "Hello world!" : Text?
>> tmpfile:read_bytes()
- = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21]? : [Byte]?
+ = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21] : [Byte]?
>> tmpdir:files():has(tmpfile)
= yes
@@ -36,9 +36,9 @@ func main():
fail("Couldn't read lines in $tmpfile")
>> (./does-not-exist.xxx):read()
- = !Text
+ = NULL : Text?
>> (./does-not-exist.xxx):read_bytes()
- = ![Byte]
+ = NULL : [Byte]?
if lines := (./does-not-exist.xxx):by_line():
fail("I could read lines in a nonexistent file")
else:
@@ -84,7 +84,7 @@ func main():
>> (./foo.txt):ends_with(".txt")
= yes
>> (./foo.txt):matches($|{..}/foo{..}|)
- = [".", ".txt"]?
+ = [".", ".txt"] : [Text]?
>> (./foo.txt):replace($/.txt/, ".md")
= (./foo.md)
diff --git a/test/reductions.tm b/test/reductions.tm
index a844081f..23ccf097 100644
--- a/test/reductions.tm
+++ b/test/reductions.tm
@@ -2,10 +2,10 @@ struct Foo(x,y:Int)
func main():
>> (+: [10, 20, 30])
- = 60?
+ = 60 : Int?
>> (+: [:Int])
- = !Int
+ = NULL : Int?
>> (+: [10, 20, 30]) or 0
= 60
@@ -14,10 +14,10 @@ func main():
= 0
>> (_max_: [3, 5, 2, 1, 4])
- = 5?
+ = 5 : Int?
>> (_max_:abs(): [1, -10, 5])
- = -10?
+ = -10 : Int?
>> (_max_: [Foo(0, 0), Foo(1, 0), Foo(0, 10)])!
= Foo(x=1, y=0)
@@ -37,7 +37,7 @@ func main():
= yes
>> (<=: [:Int])
- = !Bool
+ = NULL : Bool?
>> (<=: [5, 4, 3, 2, 1])!
= no
diff --git a/test/structs.tm b/test/structs.tm
index d4b0a87e..26b24d62 100644
--- a/test/structs.tm
+++ b/test/structs.tm
@@ -60,7 +60,7 @@ func test_text():
>> a := @CorecursiveA(b)
>> b.other = a
>> a
- = @CorecursiveA(@CorecursiveB(@~1?)?)
+ = @CorecursiveA(@CorecursiveB(@~1))
func main():
test_literals()
diff --git a/test/tables.tm b/test/tables.tm
index 650e4e18..85ce5e01 100644
--- a/test/tables.tm
+++ b/test/tables.tm
@@ -3,11 +3,11 @@ func main():
= {"one":1, "two":2}
>> t:get("one")
- = 1?
+ = 1 : Int?
>> t:get("two")
- = 2?
+ = 2 : Int?
>> t:get("???")
- = !Int
+ = NULL : Int?
>> t:get("one")!
= 1
>> t:get("???") or -1
@@ -22,7 +22,7 @@ func main():
>> t.length
= 2
>> t.fallback
- = !{Text:Int}
+ = NULL : {Text:Int}?
>> t.keys
= ["one", "two"]
@@ -33,16 +33,16 @@ func main():
= {"three":3; fallback={"one":1, "two":2}}
>> t2:get("one")
- = 1?
+ = 1 : Int?
>> t2:get("three")
- = 3?
+ = 3 : Int?
>> t2:get("???")
- = !Int
+ = NULL : Int?
>> t2.length
= 1
>> t2.fallback
- = {"one":1, "two":2}?
+ = {"one":1, "two":2} : {Text:Int}?
t2_str := ""
for k,v in t2:
diff --git a/test/text.tm b/test/text.tm
index 45d5c28f..cd00940f 100644
--- a/test/text.tm
+++ b/test/text.tm
@@ -35,7 +35,7 @@ func main():
>> Text.from_bytes([:Byte 0x41, 0x6D, 0xC3, 0xA9, 0x6C, 0x69, 0x65])!
= "Amélie"
>> Text.from_bytes([Byte(0xFF)])
- = !Text
+ = NULL : Text?
>> amelie2 := "Am$(\U65\U301)lie"
>> amelie2:split()
@@ -189,13 +189,13 @@ func main():
!! Test text:find()
>> " one two three ":find($/{id}/, start=-999)
- = !Match
+ = NULL : Match?
>> " one two three ":find($/{id}/, start=999)
- = !Match
+ = NULL : Match?
>> " one two three ":find($/{id}/)
- = Match(text="one", index=2, captures=["one"])?
+ = Match(text="one", index=2, captures=["one"]) : Match?
>> " one two three ":find($/{id}/, start=5)
- = Match(text="two", index=8, captures=["two"])?
+ = Match(text="two", index=8, captures=["two"]) : Match?
!! Test text slicing:
>> "abcdef":slice()
@@ -222,7 +222,7 @@ func main():
= ["PENGUIN"]
>> Text.from_codepoint_names(["not a valid name here buddy"])
- = !Text
+ = NULL : Text?
>> "one two; three four":find_all($/; {..}/)
= [Match(text="; three four", index=8, captures=["three four"])]
@@ -247,13 +247,13 @@ func main():
= " good(x, fn(y), BAD(z), w) "
>> "Hello":matches($/{id}/)
- = ["Hello"]?
+ = ["Hello"] : [Text]?
>> "Hello":matches($/{lower}/)
- = ![Text]
+ = NULL : [Text]?
>> "Hello":matches($/{upper}/)
- = ![Text]
+ = NULL : [Text]?
>> "Hello...":matches($/{id}/)
- = ![Text]
+ = NULL : [Text]?
if matches := "hello world":matches($/{id} {id}/):
>> matches
diff --git a/typecheck.c b/typecheck.c
index b3d96d37..2d84c622 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -484,6 +484,8 @@ type_t *get_type(env_t *env, ast_t *ast)
#pragma GCC diagnostic ignored "-Wswitch-default"
switch (ast->tag) {
case Null: {
+ if (!Match(ast, Null)->type)
+ return Type(OptionalType, .type=NULL);
type_t *t = parse_type_ast(env, Match(ast, Null)->type);
return Type(OptionalType, .type=t);
}
@@ -1061,6 +1063,9 @@ type_t *get_type(env_t *env, ast_t *ast)
if (ret->tag == AbortType)
ret = Type(VoidType);
+ if (ret->tag == OptionalType && !Match(ret, OptionalType)->type)
+ code_err(lambda->body, "This function doesn't return a specific optional type");
+
if (lambda->ret_type) {
type_t *declared = parse_type_ast(env, lambda->ret_type);
if (can_promote(ret, declared))
diff --git a/types.c b/types.c
index e03bfb6e..5c26d07a 100644
--- a/types.c
+++ b/types.c
@@ -117,7 +117,7 @@ bool type_eq(type_t *a, type_t *b)
bool type_is_a(type_t *t, type_t *req)
{
if (type_eq(t, req)) return true;
- if (req->tag == OptionalType)
+ if (req->tag == OptionalType && Match(req, OptionalType)->type)
return type_is_a(t, Match(req, OptionalType)->type);
if (t->tag == PointerType && req->tag == PointerType) {
auto t_ptr = Match(t, PointerType);
@@ -144,10 +144,14 @@ type_t *type_or_type(type_t *a, type_t *b)
{
if (!a) return b;
if (!b) return a;
- if (type_is_a(b, a)) return a;
- if (type_is_a(a, b)) return b;
+ if (a->tag == OptionalType && !Match(a, OptionalType)->type)
+ return b->tag == OptionalType ? b : Type(OptionalType, b);
+ if (b->tag == OptionalType && !Match(b, OptionalType)->type)
+ return a->tag == OptionalType ? a : Type(OptionalType, a);
if (a->tag == ReturnType && b->tag == ReturnType)
return Type(ReturnType, .ret=type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret));
+ if (type_is_a(b, a)) return a;
+ if (type_is_a(a, b)) return b;
if (a->tag == AbortType || a->tag == ReturnType) return non_optional(b);
if (b->tag == AbortType || b->tag == ReturnType) return non_optional(a);
if ((a->tag == IntType || a->tag == NumType) && (b->tag == IntType || b->tag == NumType)) {