aboutsummaryrefslogtreecommitdiff
path: root/src/compile
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-08-24 15:56:38 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-08-24 15:56:38 -0400
commite920d306fb51c16fd473033b64aa5ba5c3e07409 (patch)
treeab0a7cb47ad050f278981be1e0a4edc19b8daaee /src/compile
parentc62c3200c7078f58b218def14c54bbfd26677069 (diff)
Split into more files: promotion, sets, tables, pointers, functions.
Diffstat (limited to 'src/compile')
-rw-r--r--src/compile/enums.h1
-rw-r--r--src/compile/functions.c96
-rw-r--r--src/compile/functions.h6
-rw-r--r--src/compile/integers.c75
-rw-r--r--src/compile/integers.h6
-rw-r--r--src/compile/optionals.c85
-rw-r--r--src/compile/optionals.h7
-rw-r--r--src/compile/pointers.c21
-rw-r--r--src/compile/pointers.h3
-rw-r--r--src/compile/promotion.c195
-rw-r--r--src/compile/promotion.h7
-rw-r--r--src/compile/sets.c51
-rw-r--r--src/compile/sets.h7
-rw-r--r--src/compile/structs.h1
-rw-r--r--src/compile/tables.c76
-rw-r--r--src/compile/tables.h6
16 files changed, 643 insertions, 0 deletions
diff --git a/src/compile/enums.h b/src/compile/enums.h
index c272086a..21235532 100644
--- a/src/compile/enums.h
+++ b/src/compile/enums.h
@@ -5,6 +5,7 @@
#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
+#include "../types.h"
Text_t compile_empty_enum(type_t *t);
Text_t compile_enum_constructors(env_t *env, ast_t *ast);
diff --git a/src/compile/functions.c b/src/compile/functions.c
new file mode 100644
index 00000000..ac4e27ea
--- /dev/null
+++ b/src/compile/functions.c
@@ -0,0 +1,96 @@
+#include "../ast.h"
+#include "../compile.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/integers.h"
+#include "../stdlib/nums.h"
+#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
+#include "../stdlib/util.h"
+#include "../typecheck.h"
+#include "../types.h"
+#include "integers.h"
+
+public
+Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args) {
+ Table_t used_args = {};
+ Text_t code = EMPTY_TEXT;
+ env_t *default_scope = new (env_t);
+ *default_scope = *env;
+ default_scope->locals = new (Table_t, .fallback = env->namespace_bindings ? env->namespace_bindings : env->globals);
+ for (arg_t *spec_arg = spec_args; spec_arg; spec_arg = spec_arg->next) {
+ int64_t i = 1;
+ // Find keyword:
+ if (spec_arg->name) {
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (call_arg->name && streq(call_arg->name, spec_arg->name)) {
+ Text_t value;
+ if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
+ value = compile_int_to_type(env, call_arg->value, spec_arg->type);
+ } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
+ OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
+ if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
+ if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
+ value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
+ else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
+ } else {
+ env_t *arg_env = with_enum_scope(env, spec_arg->type);
+ value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
+ }
+ Table$str_set(&used_args, call_arg->name, call_arg);
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, value);
+ goto found_it;
+ }
+ }
+ }
+ // Find positional:
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (call_arg->name) continue;
+ const char *pseudoname = String(i++);
+ if (!Table$str_get(used_args, pseudoname)) {
+ Text_t value;
+ if (spec_arg->type->tag == IntType && call_arg->value->tag == Int) {
+ value = compile_int_to_type(env, call_arg->value, spec_arg->type);
+ } else if (spec_arg->type->tag == NumType && call_arg->value->tag == Int) {
+ OptionalInt_t int_val = Int$from_str(Match(call_arg->value, Int)->str);
+ if (int_val.small == 0) code_err(call_arg->value, "Failed to parse this integer");
+ if (Match(spec_arg->type, NumType)->bits == TYPE_NBITS64)
+ value = Text$from_str(String(hex_double(Num$from_int(int_val, false))));
+ else value = Text$from_str(String(hex_double((double)Num32$from_int(int_val, false)), "f"));
+ } else {
+ env_t *arg_env = with_enum_scope(env, spec_arg->type);
+ value = compile_maybe_incref(arg_env, call_arg->value, spec_arg->type);
+ }
+
+ Table$str_set(&used_args, pseudoname, call_arg);
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, value);
+ goto found_it;
+ }
+ }
+
+ if (spec_arg->default_val) {
+ if (code.length > 0) code = Texts(code, ", ");
+ code = Texts(code, compile_maybe_incref(default_scope, spec_arg->default_val, get_arg_type(env, spec_arg)));
+ goto found_it;
+ }
+
+ assert(spec_arg->name);
+ code_err(call_ast, "The required argument '", spec_arg->name, "' was not provided");
+ found_it:
+ continue;
+ }
+
+ int64_t i = 1;
+ for (arg_ast_t *call_arg = call_args; call_arg; call_arg = call_arg->next) {
+ if (call_arg->name) {
+ if (!Table$str_get(used_args, call_arg->name))
+ code_err(call_arg->value, "There is no argument with the name '", call_arg->name, "'");
+ } else {
+ const char *pseudoname = String(i++);
+ if (!Table$str_get(used_args, pseudoname)) code_err(call_arg->value, "This is one argument too many!");
+ }
+ }
+ return code;
+}
diff --git a/src/compile/functions.h b/src/compile/functions.h
new file mode 100644
index 00000000..1d2362a4
--- /dev/null
+++ b/src/compile/functions.h
@@ -0,0 +1,6 @@
+
+#include "../ast.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+
+Text_t compile_arguments(env_t *env, ast_t *call_ast, arg_t *spec_args, arg_ast_t *call_args);
diff --git a/src/compile/integers.c b/src/compile/integers.c
new file mode 100644
index 00000000..97c9c61a
--- /dev/null
+++ b/src/compile/integers.c
@@ -0,0 +1,75 @@
+#include <gmp.h>
+
+#include "../ast.h"
+#include "../compile.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/integers.h"
+#include "../stdlib/text.h"
+#include "../typecheck.h"
+#include "../types.h"
+#include "promotion.h"
+
+Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target) {
+ if (ast->tag != Int) {
+ Text_t code = compile(env, ast);
+ type_t *actual_type = get_type(env, ast);
+ if (!promote(env, ast, &code, actual_type, target))
+ code_err(ast, "I couldn't promote this ", type_to_str(actual_type), " to a ", type_to_str(target));
+ return code;
+ }
+
+ if (target->tag == BigIntType) return compile(env, ast);
+
+ if (target->tag == OptionalType && Match(target, OptionalType)->type)
+ return compile_int_to_type(env, ast, Match(target, OptionalType)->type);
+
+ const char *literal = Match(ast, Int)->str;
+ OptionalInt_t int_val = Int$from_str(literal);
+ if (int_val.small == 0) code_err(ast, "Failed to parse this integer");
+
+ mpz_t i;
+ mpz_init_set_int(i, int_val);
+
+ char *c_literal;
+ if (strncmp(literal, "0x", 2) == 0 || strncmp(literal, "0X", 2) == 0 || strncmp(literal, "0b", 2) == 0) {
+ gmp_asprintf(&c_literal, "0x%ZX", i);
+ } else if (strncmp(literal, "0o", 2) == 0) {
+ gmp_asprintf(&c_literal, "%#Zo", i);
+ } else {
+ gmp_asprintf(&c_literal, "%#Zd", i);
+ }
+
+ if (target->tag == ByteType) {
+ if (mpz_cmp_si(i, UINT8_MAX) <= 0 && mpz_cmp_si(i, 0) >= 0) return Texts("(Byte_t)(", c_literal, ")");
+ code_err(ast, "This integer cannot fit in a byte");
+ } else if (target->tag == NumType) {
+ if (Match(target, NumType)->bits == TYPE_NBITS64) {
+ return Texts("N64(", c_literal, ")");
+ } else {
+ return Texts("N32(", c_literal, ")");
+ }
+ } else if (target->tag == IntType) {
+ int64_t target_bits = (int64_t)Match(target, IntType)->bits;
+ switch (target_bits) {
+ case TYPE_IBITS64:
+ if (mpz_cmp_si(i, INT64_MIN) == 0) return Text("I64(INT64_MIN)");
+ if (mpz_cmp_si(i, INT64_MAX) <= 0 && mpz_cmp_si(i, INT64_MIN) >= 0) return Texts("I64(", c_literal, "L)");
+ break;
+ case TYPE_IBITS32:
+ if (mpz_cmp_si(i, INT32_MAX) <= 0 && mpz_cmp_si(i, INT32_MIN) >= 0) return Texts("I32(", c_literal, ")");
+ break;
+ case TYPE_IBITS16:
+ if (mpz_cmp_si(i, INT16_MAX) <= 0 && mpz_cmp_si(i, INT16_MIN) >= 0) return Texts("I16(", c_literal, ")");
+ break;
+ case TYPE_IBITS8:
+ if (mpz_cmp_si(i, INT8_MAX) <= 0 && mpz_cmp_si(i, INT8_MIN) >= 0) return Texts("I8(", c_literal, ")");
+ break;
+ default: break;
+ }
+ code_err(ast, "This integer cannot fit in a ", target_bits, "-bit value");
+ } else {
+ code_err(ast, "I don't know how to compile this to a ", type_to_str(target));
+ }
+ return EMPTY_TEXT;
+}
diff --git a/src/compile/integers.h b/src/compile/integers.h
new file mode 100644
index 00000000..f4479b3e
--- /dev/null
+++ b/src/compile/integers.h
@@ -0,0 +1,6 @@
+
+#include "../ast.h"
+#include "../environment.h"
+#include "../types.h"
+
+Text_t compile_int_to_type(env_t *env, ast_t *ast, type_t *target);
diff --git a/src/compile/optionals.c b/src/compile/optionals.c
new file mode 100644
index 00000000..4b360b31
--- /dev/null
+++ b/src/compile/optionals.c
@@ -0,0 +1,85 @@
+#include "../compile.h"
+#include "../environment.h"
+#include "../naming.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/text.h"
+#include "../stdlib/util.h"
+#include "../types.h"
+
+Text_t optional_into_nonnone(type_t *t, Text_t value) {
+ if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
+ switch (t->tag) {
+ case IntType:
+ case ByteType: return Texts(value, ".value");
+ case StructType:
+ if (t == PATH_TYPE || t == PATH_TYPE_TYPE) return value;
+ return Texts(value, ".value");
+ default: return value;
+ }
+}
+
+public
+Text_t promote_to_optional(type_t *t, Text_t code) {
+ if (t == PATH_TYPE || t == PATH_TYPE_TYPE) {
+ return code;
+ } else if (t->tag == IntType) {
+ switch (Match(t, IntType)->bits) {
+ case TYPE_IBITS8: return Texts("((OptionalInt8_t){.value=", code, "})");
+ case TYPE_IBITS16: return Texts("((OptionalInt16_t){.value=", code, "})");
+ case TYPE_IBITS32: return Texts("((OptionalInt32_t){.value=", code, "})");
+ case TYPE_IBITS64: return Texts("((OptionalInt64_t){.value=", code, "})");
+ default: errx(1, "Unsupported in type: %s", type_to_str(t));
+ }
+ return code;
+ } else if (t->tag == ByteType) {
+ return Texts("((OptionalByte_t){.value=", code, "})");
+ } else if (t->tag == StructType) {
+ return Texts("({ ", compile_type(Type(OptionalType, .type = t)), " nonnull = {.value=", code,
+ "}; nonnull.is_none = false; nonnull; })");
+ } else {
+ return code;
+ }
+}
+
+public
+Text_t compile_none(type_t *t) {
+ if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");
+
+ if (t->tag == OptionalType) t = Match(t, OptionalType)->type;
+
+ if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a `none` value with no type");
+
+ if (t == PATH_TYPE) return Text("NONE_PATH");
+ else if (t == PATH_TYPE_TYPE) return Text("((OptionalPathType_t){})");
+
+ switch (t->tag) {
+ case BigIntType: return Text("NONE_INT");
+ case IntType: {
+ switch (Match(t, IntType)->bits) {
+ case TYPE_IBITS8: return Text("NONE_INT8");
+ case TYPE_IBITS16: return Text("NONE_INT16");
+ case TYPE_IBITS32: return Text("NONE_INT32");
+ case TYPE_IBITS64: return Text("NONE_INT64");
+ default: errx(1, "Invalid integer bit size");
+ }
+ break;
+ }
+ case BoolType: return Text("NONE_BOOL");
+ case ByteType: return Text("NONE_BYTE");
+ case ListType: return Text("NONE_LIST");
+ case TableType: return Text("NONE_TABLE");
+ case SetType: return Text("NONE_TABLE");
+ case TextType: return Text("NONE_TEXT");
+ case CStringType: return Text("NULL");
+ case PointerType: return Texts("((", compile_type(t), ")NULL)");
+ case ClosureType: return Text("NONE_CLOSURE");
+ case NumType: return Text("nan(\"none\")");
+ case StructType: return Texts("((", compile_type(Type(OptionalType, .type = t)), "){.is_none=true})");
+ case EnumType: {
+ env_t *enum_env = Match(t, EnumType)->env;
+ return Texts("((", compile_type(t), "){", namespace_name(enum_env, enum_env->namespace, Text("none")), "})");
+ }
+ default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: ", type_to_str(t));
+ }
+ return EMPTY_TEXT;
+}
diff --git a/src/compile/optionals.h b/src/compile/optionals.h
new file mode 100644
index 00000000..ec5d1954
--- /dev/null
+++ b/src/compile/optionals.h
@@ -0,0 +1,7 @@
+#include "../stdlib/datatypes.h"
+#include "../types.h"
+
+Text_t check_none(type_t *t, Text_t value);
+Text_t optional_into_nonnone(type_t *t, Text_t value);
+Text_t promote_to_optional(type_t *t, Text_t code);
+Text_t compile_none(type_t *t);
diff --git a/src/compile/pointers.c b/src/compile/pointers.c
index cb11844d..ee67b18d 100644
--- a/src/compile/pointers.c
+++ b/src/compile/pointers.c
@@ -10,7 +10,9 @@
#include "../environment.h"
#include "../stdlib/text.h"
#include "../typecheck.h"
+#include "promotion.h"
+public
Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool needs_incref) {
Text_t val = compile(env, ast);
type_t *t = get_type(env, ast);
@@ -46,3 +48,22 @@ Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bo
return val;
}
+
+public
+Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type) {
+ // TODO: for constructors, do new(T, ...) instead of heap((T){...})
+ type_t *pointed = Match(pointer_type, PointerType)->pointed;
+ switch (ast->tag) {
+ case HeapAllocate: {
+ return Texts("heap(", compile_to_type(env, Match(ast, HeapAllocate)->value, pointed), ")");
+ }
+ case StackReference: {
+ ast_t *subject = Match(ast, StackReference)->value;
+ if (can_be_mutated(env, subject) && type_eq(pointed, get_type(env, subject)))
+ return Texts("(&", compile_lvalue(env, subject), ")");
+ else return Texts("stack(", compile_to_type(env, subject, pointed), ")");
+ }
+ default: code_err(ast, "Not an allocation!");
+ }
+ return EMPTY_TEXT;
+}
diff --git a/src/compile/pointers.h b/src/compile/pointers.h
index 306c3019..49e73c1c 100644
--- a/src/compile/pointers.h
+++ b/src/compile/pointers.h
@@ -1,6 +1,9 @@
#include <stdbool.h>
+#include "../ast.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
+#include "../types.h"
Text_t compile_to_pointer_depth(env_t *env, ast_t *ast, int64_t target_depth, bool needs_incref);
+Text_t compile_typed_allocation(env_t *env, ast_t *ast, type_t *pointer_type);
diff --git a/src/compile/promotion.c b/src/compile/promotion.c
new file mode 100644
index 00000000..fbac5888
--- /dev/null
+++ b/src/compile/promotion.c
@@ -0,0 +1,195 @@
+#include "promotion.h"
+#include "../ast.h"
+#include "../compile.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/text.h"
+#include "../typecheck.h"
+#include "../types.h"
+#include "integers.h"
+#include "lists.h"
+#include "optionals.h"
+#include "pointers.h"
+#include "sets.h"
+#include "tables.h"
+
+static Text_t quoted_str(const char *str) { return Text$quoted(Text$from_str(str), false, Text("\"")); }
+
+public
+bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed) {
+ if (type_eq(actual, needed)) return true;
+
+ if (!can_promote(actual, needed)) return false;
+
+ if (needed->tag == ClosureType && actual->tag == FunctionType) {
+ *code = Texts("((Closure_t){", *code, ", NULL})");
+ return true;
+ }
+
+ // Empty promotion:
+ type_t *more_complete = most_complete_type(actual, needed);
+ if (more_complete) return true;
+
+ // Optional promotion:
+ if (needed->tag == OptionalType && type_eq(actual, Match(needed, OptionalType)->type)) {
+ *code = promote_to_optional(actual, *code);
+ return true;
+ }
+
+ // Optional -> Bool promotion
+ if (actual->tag == OptionalType && needed->tag == BoolType) {
+ *code = Texts("(!", check_none(actual, *code), ")");
+ return true;
+ }
+
+ // Lang to Text_t:
+ if (actual->tag == TextType && needed->tag == TextType && streq(Match(needed, TextType)->lang, "Text")) return true;
+
+ // Automatic optional checking for nums:
+ if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) {
+ int64_t line = get_line_number(ast->file, ast->start);
+ *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (",
+ check_none(actual, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(",
+ quoted_str(ast->file->filename), ", ", String((int64_t)(ast->start - ast->file->text)), ", ",
+ String((int64_t)(ast->end - ast->file->text)), ", ",
+ "\"This was expected to be a value, but it's none\");\n",
+ optional_into_nonnone(actual, Text("opt")), "; })");
+ return true;
+ }
+
+ // Numeric promotions/demotions
+ if ((is_numeric_type(actual) || actual->tag == BoolType) && (is_numeric_type(needed) || needed->tag == BoolType)) {
+ arg_ast_t *args = new (arg_ast_t, .value = LiteralCode(*code, .type = actual));
+ binding_t *constructor = get_constructor(
+ env, needed, args, env->current_type != NULL && type_eq(env->current_type, value_type(needed)));
+ if (constructor) {
+ DeclareMatch(fn, constructor->type, FunctionType);
+ if (fn->args->next == NULL) {
+ *code = Texts(constructor->code, "(", compile_arguments(env, ast, fn->args, args), ")");
+ return true;
+ }
+ }
+ }
+
+ if (needed->tag == EnumType) {
+ const char *tag = enum_single_value_tag(needed, actual);
+ binding_t *b = get_binding(Match(needed, EnumType)->env, tag);
+ assert(b && b->type->tag == FunctionType);
+ // Single-value enum constructor:
+ if (!promote(env, ast, code, actual, Match(b->type, FunctionType)->args->type)) return false;
+ *code = Texts(b->code, "(", *code, ")");
+ return true;
+ }
+
+ // Text_t to C String
+ if (actual->tag == TextType && type_eq(actual, TEXT_TYPE) && needed->tag == CStringType) {
+ *code = Texts("Text$as_c_string(", *code, ")");
+ return true;
+ }
+
+ // Automatic dereferencing:
+ if (actual->tag == PointerType && can_promote(Match(actual, PointerType)->pointed, needed)) {
+ *code = Texts("*(", *code, ")");
+ return promote(env, ast, code, Match(actual, PointerType)->pointed, needed);
+ }
+
+ // Stack ref promotion:
+ if (actual->tag == PointerType && needed->tag == PointerType) return true;
+
+ // Cross-promotion between tables with default values and without
+ if (needed->tag == TableType && actual->tag == TableType) return true;
+
+ if (needed->tag == ClosureType && actual->tag == ClosureType) return true;
+
+ if (needed->tag == FunctionType && actual->tag == FunctionType) {
+ *code = Texts("(", compile_type(needed), ")", *code);
+ return true;
+ }
+
+ // Set -> List promotion:
+ if (needed->tag == ListType && actual->tag == SetType
+ && type_eq(Match(needed, ListType)->item_type, Match(actual, SetType)->item_type)) {
+ *code = Texts("(", *code, ").entries");
+ return true;
+ }
+
+ return false;
+}
+
+public
+Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t) {
+ assert(!is_incomplete_type(t));
+ if (ast->tag == Int && is_numeric_type(non_optional(t))) {
+ return compile_int_to_type(env, ast, t);
+ } else if (ast->tag == Num && t->tag == NumType) {
+ double n = Match(ast, Num)->n;
+ switch (Match(t, NumType)->bits) {
+ case TYPE_NBITS64: return Text$from_str(String(hex_double(n)));
+ case TYPE_NBITS32: return Text$from_str(String(hex_double(n), "f"));
+ default: code_err(ast, "This is not a valid number bit width");
+ }
+ } else if (ast->tag == None) {
+ if (t->tag != OptionalType) code_err(ast, "This is not supposed to be an optional type");
+ else if (Match(t, OptionalType)->type == NULL)
+ code_err(ast, "I don't know what kind of `none` this is supposed to "
+ "be!\nPlease "
+ "tell me by declaring a variable like `foo : Type = none`");
+ return compile_none(t);
+ } else if (t->tag == PointerType && (ast->tag == HeapAllocate || ast->tag == StackReference)) {
+ return compile_typed_allocation(env, ast, t);
+ } else if (t->tag == ListType && ast->tag == List) {
+ return compile_typed_list(env, ast, t);
+ } else if (t->tag == TableType && ast->tag == Table) {
+ return compile_typed_table(env, ast, t);
+ } else if (t->tag == SetType && ast->tag == Set) {
+ return compile_typed_set(env, ast, t);
+ }
+
+ type_t *actual = get_type(env, ast);
+
+ // Edge case: there are some situations where a method call needs to have
+ // the `self` value get compiled to a specific type that can't be fully
+ // inferred from the expression itself. We can infer what the specific type
+ // should be from what we know the specific type of the return value is,
+ // but it requires a bit of special logic.
+ // For example:
+ // x : [Int?] = [none].sorted()
+ // Here, we know that `[none]` is `[Int?]`, but we need to thread that
+ // information through the compiler using an `ExplicitlyTyped` node.
+ if (ast->tag == MethodCall) {
+ DeclareMatch(methodcall, ast, MethodCall);
+ type_t *self_type = get_type(env, methodcall->self);
+ // Currently, this is only implemented for cases where you have the
+ // return type and the self type equal to each other, because that's the
+ // main case I care about with list and set methods (e.g.
+ // `List.sorted()`)
+ if (is_incomplete_type(self_type) && type_eq(self_type, actual)) {
+ type_t *completed_self = most_complete_type(self_type, t);
+ if (completed_self) {
+ ast_t *explicit_self =
+ WrapAST(methodcall->self, ExplicitlyTyped, .ast = methodcall->self, .type = completed_self);
+ ast_t *new_methodcall =
+ WrapAST(ast, MethodCall, .self = explicit_self, .name = methodcall->name, .args = methodcall->args);
+ return compile_to_type(env, new_methodcall, t);
+ }
+ }
+ }
+
+ // Promote values to views-of-values if needed:
+ if (t->tag == PointerType && Match(t, PointerType)->is_stack && actual->tag != PointerType)
+ return Texts("stack(", compile_to_type(env, ast, Match(t, PointerType)->pointed), ")");
+
+ if (!is_incomplete_type(actual)) {
+ Text_t code = compile(env, ast);
+ if (promote(env, ast, &code, actual, t)) return code;
+ }
+
+ arg_ast_t *constructor_args = new (arg_ast_t, .value = ast);
+ binding_t *constructor = get_constructor(env, t, constructor_args, true);
+ if (constructor) {
+ arg_t *arg_spec = Match(constructor->type, FunctionType)->args;
+ return Texts(constructor->code, "(", compile_arguments(env, ast, arg_spec, constructor_args), ")");
+ }
+
+ code_err(ast, "I expected a ", type_to_str(t), " here, but this is a ", type_to_str(actual));
+}
diff --git a/src/compile/promotion.h b/src/compile/promotion.h
new file mode 100644
index 00000000..2cfe0cbf
--- /dev/null
+++ b/src/compile/promotion.h
@@ -0,0 +1,7 @@
+
+#include "../ast.h"
+#include "../environment.h"
+#include "../types.h"
+
+bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *needed);
+Text_t compile_to_type(env_t *env, ast_t *ast, type_t *t);
diff --git a/src/compile/sets.c b/src/compile/sets.c
new file mode 100644
index 00000000..a1471a5a
--- /dev/null
+++ b/src/compile/sets.c
@@ -0,0 +1,51 @@
+
+#include "../ast.h"
+#include "../compile.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/text.h"
+#include "../types.h"
+#include "promotion.h"
+
+static ast_t *add_to_set_comprehension(ast_t *item, ast_t *subject) {
+ return WrapAST(item, MethodCall, .name = "add", .self = subject, .args = new (arg_ast_t, .value = item));
+}
+
+Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type) {
+ DeclareMatch(set, ast, Set);
+ if (!set->items) return Text("((Table_t){})");
+
+ type_t *item_type = Match(set_type, SetType)->item_type;
+
+ int64_t n = 0;
+ for (ast_list_t *item = set->items; item; item = item->next) {
+ ++n;
+ if (item->ast->tag == Comprehension) goto set_comprehension;
+ }
+
+ { // No comprehension:
+ Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", String(n));
+ env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env;
+ for (ast_list_t *item = set->items; item; item = item->next) {
+ code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type));
+ }
+ return Texts(code, ")");
+ }
+
+set_comprehension: {
+ static int64_t comp_num = 1;
+ env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : fresh_scope(env);
+ const char *comprehension_name = String("set$", comp_num++);
+ ast_t *comprehension_var =
+ LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = set_type, .is_stack = true));
+ Text_t code = Texts("({ Table_t ", comprehension_name, " = {};");
+ Closure_t comp_action = {.fn = add_to_set_comprehension, .userdata = comprehension_var};
+ scope->comprehension_action = &comp_action;
+ for (ast_list_t *item = set->items; item; item = item->next) {
+ if (item->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, item->ast));
+ else code = Texts(code, compile_statement(env, add_to_set_comprehension(item->ast, comprehension_var)));
+ }
+ code = Texts(code, " ", comprehension_name, "; })");
+ return code;
+}
+}
diff --git a/src/compile/sets.h b/src/compile/sets.h
new file mode 100644
index 00000000..bfbcf3b4
--- /dev/null
+++ b/src/compile/sets.h
@@ -0,0 +1,7 @@
+
+#include "../ast.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../types.h"
+
+Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type);
diff --git a/src/compile/structs.h b/src/compile/structs.h
index 8185b561..42c95003 100644
--- a/src/compile/structs.h
+++ b/src/compile/structs.h
@@ -4,6 +4,7 @@
#include "../ast.h"
#include "../environment.h"
+#include "../types.h"
Text_t compile_empty_struct(type_t *t);
Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_t *fields, bool is_secret,
diff --git a/src/compile/tables.c b/src/compile/tables.c
new file mode 100644
index 00000000..0ddd310c
--- /dev/null
+++ b/src/compile/tables.c
@@ -0,0 +1,76 @@
+
+#include "../ast.h"
+#include "../compile.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/text.h"
+#include "../types.h"
+#include "promotion.h"
+
+static ast_t *add_to_table_comprehension(ast_t *entry, ast_t *subject) {
+ DeclareMatch(e, entry, TableEntry);
+ return WrapAST(entry, MethodCall, .name = "set", .self = subject,
+ .args = new (arg_ast_t, .value = e->key, .next = new (arg_ast_t, .value = e->value)));
+}
+
+Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) {
+ DeclareMatch(table, ast, Table);
+ if (!table->entries) {
+ Text_t code = Text("((Table_t){");
+ if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), ")");
+ return Texts(code, "})");
+ }
+
+ type_t *key_t = Match(table_type, TableType)->key_type;
+ type_t *value_t = Match(table_type, TableType)->value_type;
+
+ if (value_t->tag == OptionalType)
+ code_err(ast, "Tables whose values are optional (", type_to_str(value_t), ") are not currently supported.");
+
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
+ if (entry->ast->tag == Comprehension) goto table_comprehension;
+ }
+
+ { // No comprehension:
+ env_t *key_scope = key_t->tag == EnumType ? with_enum_scope(env, key_t) : env;
+ env_t *value_scope = value_t->tag == EnumType ? with_enum_scope(env, value_t) : env;
+ Text_t code = Texts("Table(", compile_type(key_t), ", ", compile_type(value_t), ", ", compile_type_info(key_t),
+ ", ", compile_type_info(value_t));
+ if (table->fallback) code = Texts(code, ", /*fallback:*/ heap(", compile(env, table->fallback), ")");
+ else code = Texts(code, ", /*fallback:*/ NULL");
+
+ size_t n = 0;
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next)
+ ++n;
+ code = Texts(code, ", ", String((int64_t)n));
+
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
+ DeclareMatch(e, entry->ast, TableEntry);
+ code = Texts(code, ",\n\t{", compile_to_type(key_scope, e->key, key_t), ", ",
+ compile_to_type(value_scope, e->value, value_t), "}");
+ }
+ return Texts(code, ")");
+ }
+
+table_comprehension: {
+ static int64_t comp_num = 1;
+ env_t *scope = fresh_scope(env);
+ const char *comprehension_name = String("table$", comp_num++);
+ ast_t *comprehension_var =
+ LiteralCode(Texts("&", comprehension_name), .type = Type(PointerType, .pointed = table_type, .is_stack = true));
+
+ Text_t code = Texts("({ Table_t ", comprehension_name, " = {");
+ if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), "), ");
+
+ code = Texts(code, "};");
+
+ Closure_t comp_action = {.fn = add_to_table_comprehension, .userdata = comprehension_var};
+ scope->comprehension_action = &comp_action;
+ for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
+ if (entry->ast->tag == Comprehension) code = Texts(code, "\n", compile_statement(scope, entry->ast));
+ else code = Texts(code, compile_statement(env, add_to_table_comprehension(entry->ast, comprehension_var)));
+ }
+ code = Texts(code, " ", comprehension_name, "; })");
+ return code;
+}
+}
diff --git a/src/compile/tables.h b/src/compile/tables.h
new file mode 100644
index 00000000..c8c19cb1
--- /dev/null
+++ b/src/compile/tables.h
@@ -0,0 +1,6 @@
+#include "../ast.h"
+#include "../environment.h"
+#include "../stdlib/datatypes.h"
+#include "../types.h"
+
+Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type);