728 lines
30 KiB
C
728 lines
30 KiB
C
// Logic for getting a type from an AST node
|
|
#include <gc.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "ast.h"
|
|
#include "environment.h"
|
|
#include "parse.h"
|
|
#include "typecheck.h"
|
|
#include "types.h"
|
|
#include "util.h"
|
|
|
|
type_t *parse_type_ast(env_t *env, type_ast_t *ast)
|
|
{
|
|
switch (ast->tag) {
|
|
case VarTypeAST: {
|
|
const char *name = Match(ast, VarTypeAST)->name;
|
|
type_t *t = Table_str_get(env->types, name);
|
|
if (t) return t;
|
|
code_err(ast, "I don't know a type with the name '%s'", name);
|
|
}
|
|
case PointerTypeAST: {
|
|
auto ptr = Match(ast, PointerTypeAST);
|
|
type_t *pointed_t = parse_type_ast(env, ptr->pointed);
|
|
if (pointed_t->tag == VoidType)
|
|
code_err(ast, "Void pointers are not supported. You probably meant 'Memory' instead of 'Void'");
|
|
return Type(PointerType, .is_optional=ptr->is_optional, .pointed=pointed_t, .is_stack=ptr->is_stack, .is_readonly=ptr->is_readonly);
|
|
}
|
|
case ArrayTypeAST: {
|
|
type_ast_t *item_type = Match(ast, ArrayTypeAST)->item;
|
|
type_t *item_t = parse_type_ast(env, item_type);
|
|
if (!item_t) code_err(item_type, "I can't figure out what this type is.");
|
|
if (has_stack_memory(item_t))
|
|
code_err(item_type, "Arrays can't have stack references because the array may outlive the stack frame.");
|
|
return Type(ArrayType, .item_type=item_t);
|
|
}
|
|
case TableTypeAST: {
|
|
type_ast_t *key_type_ast = Match(ast, TableTypeAST)->key;
|
|
type_t *key_type = parse_type_ast(env, key_type_ast);
|
|
if (!key_type) code_err(key_type_ast, "I can't figure out what type this is.");
|
|
if (has_stack_memory(key_type))
|
|
code_err(key_type_ast, "Tables can't have stack references because the array may outlive the stack frame.");
|
|
type_ast_t *val_type_ast = Match(ast, TableTypeAST)->value;
|
|
type_t *val_type = parse_type_ast(env, val_type_ast);
|
|
if (!val_type) code_err(val_type_ast, "I can't figure out what type this is.");
|
|
if (has_stack_memory(val_type))
|
|
code_err(val_type_ast, "Tables can't have stack references because the array may outlive the stack frame.");
|
|
return Type(TableType, .key_type=key_type, .value_type=val_type);
|
|
}
|
|
case FunctionTypeAST: {
|
|
auto fn = Match(ast, FunctionTypeAST);
|
|
type_t *ret_t = parse_type_ast(env, fn->ret);
|
|
if (has_stack_memory(ret_t))
|
|
code_err(fn->ret, "Functions are not allowed to return stack references, because the reference may no longer exist on the stack.");
|
|
arg_t *type_args = NULL;
|
|
for (arg_ast_t *arg = fn->args; arg; arg = arg->next) {
|
|
type_args = new(arg_t, .name=arg->name, .next=type_args);
|
|
if (arg->type) {
|
|
type_args->type = parse_type_ast(env, arg->type);
|
|
} else {
|
|
type_args->default_val = arg->default_val;
|
|
type_args->type = get_type(env, arg->default_val);
|
|
}
|
|
}
|
|
REVERSE_LIST(type_args);
|
|
return Type(FunctionType, .args=type_args, .ret=ret_t);
|
|
}
|
|
case UnknownTypeAST: code_err(ast, "I don't know how to get this type");
|
|
}
|
|
code_err(ast, "I don't know how to get this type");
|
|
}
|
|
|
|
type_t *get_math_type(env_t *env, ast_t *ast, type_t *lhs_t, type_t *rhs_t)
|
|
{
|
|
(void)env;
|
|
if (lhs_t->tag == IntType && rhs_t->tag == IntType)
|
|
return Match(lhs_t, IntType)->bits >= Match(rhs_t, IntType)->bits ? lhs_t : rhs_t;
|
|
else if (lhs_t->tag == NumType && rhs_t->tag == NumType)
|
|
return Match(lhs_t, NumType)->bits >= Match(rhs_t, NumType)->bits ? lhs_t : rhs_t;
|
|
else if (lhs_t->tag == NumType && rhs_t->tag == IntType)
|
|
return lhs_t;
|
|
else if (rhs_t->tag == NumType && lhs_t->tag == IntType)
|
|
return rhs_t;
|
|
|
|
code_err(ast, "Math operations between %T and %T are not supported", lhs_t, rhs_t);
|
|
}
|
|
|
|
void bind_statement(env_t *env, ast_t *statement)
|
|
{
|
|
switch (statement->tag) {
|
|
case DocTest: {
|
|
bind_statement(env, Match(statement, DocTest)->expr);
|
|
break;
|
|
}
|
|
case Declare: {
|
|
auto decl = Match(statement, Declare);
|
|
type_t *type = get_type(env, decl->value);
|
|
set_binding(env, Match(decl->var, Var)->name, new(binding_t, .type=type));
|
|
break;
|
|
}
|
|
case StructDef: {
|
|
auto def = Match(statement, StructDef);
|
|
arg_t *fields = NULL;
|
|
type_t *type = Type(StructType, .name=def->name, .fields=fields); // placeholder
|
|
for (arg_ast_t *field_ast = def->fields; field_ast; field_ast = field_ast->next) {
|
|
type_t *field_t = parse_type_ast(env, field_ast->type);
|
|
fields = new(arg_t, .name=field_ast->name, .type=field_t, .default_val=field_ast->default_val, .next=fields);
|
|
}
|
|
REVERSE_LIST(fields);
|
|
type->__data.StructType.fields = fields; // populate placeholder
|
|
Table_str_set(env->types, def->name, type);
|
|
|
|
if (!type) code_err(statement, "I couldn't get this type");
|
|
type_t *constructor_t = Type(FunctionType, .args=Match(type, StructType)->fields, .ret=type);
|
|
set_binding(env, def->name, new(binding_t, .type=constructor_t));
|
|
break;
|
|
}
|
|
case EnumDef: {
|
|
auto def = Match(statement, EnumDef);
|
|
|
|
tag_t *tags = NULL;
|
|
type_t *type = Type(EnumType, .name=def->name, .tags=tags); // placeholder
|
|
for (tag_ast_t *tag_ast = def->tags; tag_ast; tag_ast = tag_ast->next) {
|
|
arg_t *fields = NULL;
|
|
for (arg_ast_t *field_ast = tag_ast->fields; field_ast; field_ast = field_ast->next) {
|
|
type_t *field_t = parse_type_ast(env, field_ast->type);
|
|
fields = new(arg_t, .name=field_ast->name, .type=field_t, .default_val=field_ast->default_val, .next=fields);
|
|
}
|
|
REVERSE_LIST(fields);
|
|
type_t *tag_type = Type(StructType, .name=heap_strf("%s$%s", def->name, tag_ast->name), .fields=fields);
|
|
tags = new(tag_t, .name=tag_ast->name, .tag_value=tag_ast->value, .type=tag_type, .next=tags);
|
|
}
|
|
REVERSE_LIST(tags);
|
|
type->__data.EnumType.tags = tags;
|
|
|
|
for (tag_t *tag = tags; tag; tag = tag->next) {
|
|
const char *name = heap_strf("%s__%s", def->name, tag->name);
|
|
type_t *constructor_t = Type(FunctionType, .args=Match(tag->type, StructType)->fields, .ret=type);
|
|
set_binding(env, name, new(binding_t, .type=constructor_t));
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
static type_t *get_function_def_type(env_t *env, ast_t *ast)
|
|
{
|
|
auto fn = Match(ast, FunctionDef);
|
|
arg_t *args = NULL;
|
|
env_t *scope = fresh_scope(env);
|
|
for (arg_ast_t *arg = fn->args; arg; arg = arg->next) {
|
|
type_t *t = arg->type ? parse_type_ast(env, arg->type) : get_type(env, arg->default_val);
|
|
args = new(arg_t, .name=arg->name, .type=t, .next=args);
|
|
set_binding(scope, arg->name, new(binding_t, .type=t));
|
|
}
|
|
REVERSE_LIST(args);
|
|
|
|
type_t *ret = parse_type_ast(scope, fn->ret_type);
|
|
if (has_stack_memory(ret))
|
|
code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
|
|
return Type(FunctionType, .args=args, .ret=ret);
|
|
}
|
|
|
|
type_t *get_type(env_t *env, ast_t *ast)
|
|
{
|
|
if (!ast) return NULL;
|
|
switch (ast->tag) {
|
|
case Nil: {
|
|
type_t *pointed = parse_type_ast(env, Match(ast, Nil)->type);
|
|
return Type(PointerType, .is_optional=true, .pointed=pointed);
|
|
}
|
|
case Bool: {
|
|
return Type(BoolType);
|
|
}
|
|
case Int: {
|
|
auto i = Match(ast, Int);
|
|
return Type(IntType, .bits=i->bits);
|
|
}
|
|
case Num: {
|
|
auto n = Match(ast, Num);
|
|
return Type(NumType, .bits=n->bits);
|
|
}
|
|
case HeapAllocate: {
|
|
type_t *pointed = get_type(env, Match(ast, HeapAllocate)->value);
|
|
if (has_stack_memory(pointed))
|
|
code_err(ast, "Stack references cannot be moved to the heap because they may outlive the stack frame they were created in.");
|
|
return Type(PointerType, .is_optional=false, .pointed=pointed);
|
|
}
|
|
case StackReference: {
|
|
ast_t *value = Match(ast, StackReference)->value;
|
|
type_t *pointed_t = get_type(env, Match(ast, StackReference)->value);
|
|
bool is_stack = true;
|
|
// References to heap members/indexes are heap pointers, e.g. v := @Vec{1,2}; &v.x
|
|
switch (value->tag) {
|
|
case FieldAccess: {
|
|
type_t *fielded_t = get_type(env, Match(value, FieldAccess)->fielded);
|
|
is_stack = fielded_t->tag == PointerType ? Match(fielded_t, PointerType)->is_stack : true;
|
|
break;
|
|
}
|
|
case Index: {
|
|
type_t *indexed_t = get_type(env, Match(value, Index)->indexed);
|
|
is_stack = indexed_t->tag == PointerType ? Match(indexed_t, PointerType)->is_stack : true;
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
return Type(PointerType, .pointed=pointed_t, .is_stack=is_stack);
|
|
}
|
|
case StringJoin: case StringLiteral: {
|
|
return Type(StringType);
|
|
}
|
|
case Var: {
|
|
auto var = Match(ast, Var);
|
|
binding_t *b = get_binding(env, var->name);
|
|
if (b) return b->type;
|
|
// const char *suggestion = spellcheck(env->bindings, name);
|
|
// if (suggestion)
|
|
// code_err(ast, "I don't know what this variable is referring to. Did you mean '%s'?", suggestion);
|
|
// else
|
|
// code_err(ast, "I don't know what this variable is referring to.");
|
|
code_err(ast, "I don't know what \"%s\" refers to", var->name);
|
|
}
|
|
case Array: {
|
|
auto array = Match(ast, Array);
|
|
type_t *item_type = NULL;
|
|
if (array->type) {
|
|
item_type = parse_type_ast(env, array->type);
|
|
} else if (array->items) {
|
|
for (ast_list_t *item = array->items; item; item = item->next) {
|
|
type_t *t2 = get_type(env, item->ast);
|
|
type_t *merged = item_type ? type_or_type(item_type, t2) : t2;
|
|
if (!merged)
|
|
code_err(item->ast,
|
|
"This array item has type %T, which is different from earlier array items which have type %T",
|
|
t2, item_type);
|
|
item_type = merged;
|
|
}
|
|
} else {
|
|
code_err(ast, "I can't figure out what type this array has because it has no members or explicit type");
|
|
}
|
|
if (has_stack_memory(item_type))
|
|
code_err(ast, "Arrays cannot hold stack references, because the array may outlive the stack frame the reference was created in.");
|
|
return Type(ArrayType, .item_type=item_type);
|
|
}
|
|
case Table: {
|
|
auto table = Match(ast, Table);
|
|
type_t *key_type = NULL, *value_type = NULL;
|
|
if (table->key_type && table->value_type) {
|
|
key_type = parse_type_ast(env, table->key_type);
|
|
value_type = parse_type_ast(env, table->value_type);
|
|
} else {
|
|
if (table->default_value)
|
|
value_type = get_type(env, table->default_value);
|
|
for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
|
|
auto table_entry = Match(entry->ast, TableEntry);
|
|
type_t *key_t = get_type(env, table_entry->key);
|
|
type_t *key_merged = key_type ? type_or_type(key_type, key_t) : key_t;
|
|
if (!key_merged)
|
|
code_err(table_entry->key,
|
|
"This table entry has type %T, which is different from earlier table entries which have type %T",
|
|
key_t, key_type);
|
|
key_type = key_merged;
|
|
|
|
type_t *value_t = get_type(env, table_entry->value);
|
|
type_t *val_merged = value_type ? type_or_type(value_type, value_t) : value_t;
|
|
if (!val_merged)
|
|
code_err(table_entry->value,
|
|
"This table entry has type %T, which is different from earlier table entries which have type %T",
|
|
value_t, value_type);
|
|
value_type = val_merged;
|
|
}
|
|
}
|
|
if (has_stack_memory(key_type) || has_stack_memory(value_type))
|
|
code_err(ast, "Tables cannot hold stack references because the table may outlive the reference's stack frame.");
|
|
return Type(TableType, .key_type=key_type, .value_type=value_type);
|
|
}
|
|
case TableEntry: {
|
|
code_err(ast, "This should not be typechecked directly");
|
|
}
|
|
case FieldAccess: {
|
|
auto access = Match(ast, FieldAccess);
|
|
type_t *fielded_t = get_type(env, access->fielded);
|
|
type_t *field_t = get_field_type(fielded_t, access->field);
|
|
if (!field_t)
|
|
code_err(ast, "%T objects don't have a field called '%s'", fielded_t, access->field);
|
|
return field_t;
|
|
}
|
|
case Index: {
|
|
auto indexing = Match(ast, Index);
|
|
type_t *indexed_t = get_type(env, indexing->indexed);
|
|
if (indexed_t->tag == PointerType && !indexing->index) {
|
|
auto ptr = Match(indexed_t, PointerType);
|
|
if (ptr->is_optional)
|
|
code_err(ast, "You're attempting to dereference a pointer whose type indicates it could be nil");
|
|
return ptr->pointed;
|
|
}
|
|
|
|
type_t *value_t = value_type(indexed_t);
|
|
switch (value_t->tag) {
|
|
case ArrayType: {
|
|
if (!indexing->index) return indexed_t;
|
|
type_t *index_t = get_type(env, indexing->index);
|
|
switch (index_t->tag) {
|
|
case IntType:
|
|
return Match(value_t, ArrayType)->item_type;
|
|
default: code_err(indexing->index, "I only know how to index lists using integers, not %T", index_t);
|
|
}
|
|
}
|
|
case TableType: {
|
|
return Match(value_t, TableType)->value_type;
|
|
}
|
|
// TODO: support ranges like (99..123)[5]
|
|
// TODO: support slicing arrays like ([1,2,3,4])[2..10]
|
|
default: {
|
|
code_err(ast, "I don't know how to index %T values", indexed_t);
|
|
}
|
|
}
|
|
}
|
|
case KeywordArg: {
|
|
return get_type(env, Match(ast, KeywordArg)->arg);
|
|
}
|
|
case FunctionCall: {
|
|
auto call = Match(ast, FunctionCall);
|
|
if (call->extern_return_type)
|
|
return parse_type_ast(env, call->extern_return_type);
|
|
type_t *fn_type_t = get_type(env, call->fn);
|
|
if (!fn_type_t)
|
|
code_err(call->fn, "I couldn't find this function");
|
|
if (fn_type_t->tag != FunctionType)
|
|
code_err(call->fn, "This isn't a function, it's a %T", fn_type_t);
|
|
auto fn_type = Match(fn_type_t, FunctionType);
|
|
return fn_type->ret;
|
|
}
|
|
case Block: {
|
|
auto block = Match(ast, Block);
|
|
ast_list_t *last = block->statements;
|
|
if (!last)
|
|
return Type(VoidType);
|
|
while (last->next)
|
|
last = last->next;
|
|
|
|
// Early out if the type is knowable without any context from the block:
|
|
switch (last->ast->tag) {
|
|
case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef:
|
|
return Type(VoidType);
|
|
default: break;
|
|
}
|
|
|
|
env_t *block_env = fresh_scope(env);
|
|
for (ast_list_t *stmt = block->statements; stmt; stmt = stmt->next) {
|
|
bind_statement(block_env, stmt->ast);
|
|
}
|
|
return get_type(block_env, last->ast);
|
|
}
|
|
case Extern: {
|
|
type_t *t = parse_type_ast(env, Match(ast, Extern)->type);
|
|
return Match(ast, Extern)->address ? Type(PointerType, .pointed=t, .is_optional=false) : t;
|
|
}
|
|
case Declare: case Assign: case DocTest: case LinkerDirective: {
|
|
return Type(VoidType);
|
|
}
|
|
case Use: {
|
|
const char *path = Match(ast, Use)->path;
|
|
return Type(PointerType, .pointed=get_file_type(env, path));
|
|
}
|
|
case Return: case Stop: case Skip: {
|
|
return Type(AbortType);
|
|
}
|
|
case Pass: return Type(VoidType);
|
|
case Length: return Type(IntType, .bits=64);
|
|
case Negative: {
|
|
type_t *t = get_type(env, Match(ast, Negative)->value);
|
|
if (t->tag == IntType || t->tag == NumType)
|
|
return t;
|
|
code_err(ast, "I only know how to get negatives of numeric types, not %T", t);
|
|
}
|
|
case Not: {
|
|
type_t *t = get_type(env, Match(ast, Not)->value);
|
|
if (t->tag == IntType || t->tag == NumType || t->tag == BoolType)
|
|
return t;
|
|
if (t->tag == PointerType && Match(t, PointerType)->is_optional)
|
|
return Type(BoolType);
|
|
code_err(ast, "I only know how to get 'not' of boolean, numeric, and optional pointer types, not %T", t);
|
|
}
|
|
case BinaryOp: {
|
|
auto binop = Match(ast, BinaryOp);
|
|
type_t *lhs_t = get_type(env, binop->lhs),
|
|
*rhs_t = get_type(env, binop->rhs);
|
|
|
|
switch (binop->op) {
|
|
case BINOP_AND: {
|
|
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
|
return lhs_t;
|
|
} else if (lhs_t->tag == BoolType && rhs_t->tag == AbortType) {
|
|
return lhs_t;
|
|
} else if (rhs_t->tag == AbortType) {
|
|
return lhs_t;
|
|
} else if (lhs_t->tag == PointerType && rhs_t->tag == PointerType) {
|
|
auto lhs_ptr = Match(lhs_t, PointerType);
|
|
auto rhs_ptr = Match(rhs_t, PointerType);
|
|
if (type_eq(lhs_ptr->pointed, rhs_ptr->pointed))
|
|
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional || rhs_ptr->is_optional,
|
|
.is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
|
|
} else if (lhs_t->tag == IntType && rhs_t->tag == IntType) {
|
|
return get_math_type(env, ast, lhs_t, rhs_t);
|
|
}
|
|
code_err(ast, "I can't figure out the type of this `and` expression because the left side is a %T, but the right side is a %T",
|
|
lhs_t, rhs_t);
|
|
}
|
|
case BINOP_OR: {
|
|
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
|
return lhs_t;
|
|
} else if (lhs_t->tag == BoolType && rhs_t->tag == AbortType) {
|
|
return lhs_t;
|
|
} else if (lhs_t->tag == IntType && rhs_t->tag == IntType) {
|
|
return get_math_type(env, ast, lhs_t, rhs_t);
|
|
} else if (lhs_t->tag == PointerType) {
|
|
auto lhs_ptr = Match(lhs_t, PointerType);
|
|
if (rhs_t->tag == AbortType) {
|
|
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=false, .is_readonly=lhs_ptr->is_readonly);
|
|
} else if (rhs_t->tag == PointerType) {
|
|
auto rhs_ptr = Match(rhs_t, PointerType);
|
|
if (type_eq(rhs_ptr->pointed, lhs_ptr->pointed))
|
|
return Type(PointerType, .pointed=lhs_ptr->pointed, .is_optional=lhs_ptr->is_optional && rhs_ptr->is_optional,
|
|
.is_readonly=lhs_ptr->is_readonly || rhs_ptr->is_readonly);
|
|
}
|
|
}
|
|
code_err(ast, "I can't figure out the type of this `or` expression because the left side is a %T, but the right side is a %T",
|
|
lhs_t, rhs_t);
|
|
}
|
|
case BINOP_XOR: {
|
|
if (lhs_t->tag == BoolType && rhs_t->tag == BoolType) {
|
|
return lhs_t;
|
|
} else if (lhs_t->tag == IntType && rhs_t->tag == IntType) {
|
|
return get_math_type(env, ast, lhs_t, rhs_t);
|
|
}
|
|
|
|
code_err(ast, "I can't figure out the type of this `xor` expression because the left side is a %T, but the right side is a %T",
|
|
lhs_t, rhs_t);
|
|
}
|
|
case BINOP_CONCAT: {
|
|
if (!type_eq(lhs_t, rhs_t))
|
|
code_err(ast, "The type on the left side of this concatenation doesn't match the right side: %T vs. %T",
|
|
lhs_t, rhs_t);
|
|
if (lhs_t->tag == ArrayType || lhs_t->tag == StringType)
|
|
return lhs_t;
|
|
|
|
code_err(ast, "Only array/string value types support concatenation, not %T", lhs_t);
|
|
}
|
|
case BINOP_EQ: case BINOP_NE: case BINOP_LT: case BINOP_LE: case BINOP_GT: case BINOP_GE: {
|
|
if (!can_promote(lhs_t, rhs_t) && !can_promote(rhs_t, lhs_t))
|
|
code_err(ast, "I can't compare these two different types: %T vs %T", lhs_t, rhs_t);
|
|
return Type(BoolType);
|
|
}
|
|
case BINOP_POWER: {
|
|
type_t *result = get_math_type(env, ast, lhs_t, rhs_t);
|
|
if (result->tag == NumType)
|
|
return result;
|
|
return Type(NumType, .bits=64);
|
|
}
|
|
default: {
|
|
return get_math_type(env, ast, lhs_t, rhs_t);
|
|
}
|
|
}
|
|
}
|
|
|
|
case Reduction: {
|
|
auto reduction = Match(ast, Reduction);
|
|
type_t *iter_t = get_type(env, reduction->iter);
|
|
type_t *value_t = iteration_value_type(iter_t);
|
|
env_t *scope = fresh_scope(env);
|
|
set_binding(scope, "$lhs", new(binding_t, .type=value_t));
|
|
set_binding(scope, "$rhs", new(binding_t, .type=value_t));
|
|
type_t *t = get_type(scope, reduction->combination);
|
|
type_t *fallback_t = get_type(env, reduction->fallback);
|
|
if (fallback_t->tag == AbortType)
|
|
return t;
|
|
else if (can_promote(fallback_t, t))
|
|
return t;
|
|
else if (can_promote(t, fallback_t))
|
|
return fallback_t;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
case UpdateAssign:
|
|
return Type(VoidType);
|
|
|
|
case Min: case Max: {
|
|
// Unsafe! These types *should* have the same fields and this saves a lot of duplicate code:
|
|
ast_t *lhs = ast->__data.Min.lhs, *rhs = ast->__data.Min.rhs;
|
|
// Okay safe again
|
|
|
|
type_t *lhs_t = get_type(env, lhs), *rhs_t = get_type(env, rhs);
|
|
type_t *t = type_or_type(lhs_t, rhs_t);
|
|
if (!t)
|
|
code_err(ast, "The two sides of this operation are not compatible: %T vs %T", lhs_t, rhs_t);
|
|
return t;
|
|
}
|
|
|
|
case Lambda: {
|
|
auto lambda = Match(ast, Lambda);
|
|
arg_t *args = NULL;
|
|
env_t *scope = fresh_scope(env);
|
|
for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) {
|
|
type_t *t = arg->type ? parse_type_ast(env, arg->type) : get_type(env, arg->default_val);
|
|
args = new(arg_t, .name=arg->name, .type=t, .next=args);
|
|
set_binding(scope, arg->name, new(binding_t, .type=t));
|
|
}
|
|
REVERSE_LIST(args);
|
|
|
|
type_t *ret = get_type(scope, lambda->body);
|
|
if (has_stack_memory(ret))
|
|
code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame.");
|
|
return Type(ClosureType, Type(FunctionType, .args=args, .ret=ret));
|
|
}
|
|
|
|
case FunctionDef: case StructDef: case EnumDef: {
|
|
return Type(VoidType);
|
|
}
|
|
|
|
case If: {
|
|
auto if_ = Match(ast, If);
|
|
type_t *true_t = get_type(env, if_->body);
|
|
if (if_->else_body) {
|
|
type_t *false_t = get_type(env, if_->else_body);
|
|
type_t *t_either = type_or_type(true_t, false_t);
|
|
if (!t_either)
|
|
code_err(if_->else_body,
|
|
"I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
|
|
true_t, false_t);
|
|
return t_either;
|
|
} else {
|
|
return Type(VoidType);
|
|
}
|
|
}
|
|
|
|
case When: {
|
|
auto when = Match(ast, When);
|
|
type_t *subject_t = get_type(env, when->subject);
|
|
if (subject_t->tag != EnumType)
|
|
code_err(when->subject, "'when' statements are only for enum types, not %T", subject_t);
|
|
|
|
tag_t * const tags = Match(subject_t, EnumType)->tags;
|
|
|
|
typedef struct match_s {
|
|
const char *name;
|
|
type_t *type;
|
|
bool handled;
|
|
struct match_s *next;
|
|
} match_t;
|
|
match_t *matches = NULL;
|
|
for (tag_t *tag = tags; tag; tag = tag->next)
|
|
matches = new(match_t, .name=tag->name, .type=tag->type, .next=matches);
|
|
|
|
type_t *overall_t = NULL;
|
|
for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
|
|
const char *tag_name = Match(clause->tag_name, Var)->name;
|
|
type_t *tag_type = NULL;
|
|
for (match_t *m = matches; m; m = m->next) {
|
|
if (streq(m->name, tag_name)) {
|
|
if (m->handled)
|
|
code_err(clause->tag_name, "This tag was already handled earlier");
|
|
m->handled = true;
|
|
tag_type = m->type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!tag_type)
|
|
code_err(clause->tag_name, "This is not a valid tag for the type %T", subject_t);
|
|
|
|
env_t *scope = env;
|
|
if (clause->var) {
|
|
scope = fresh_scope(scope);
|
|
set_binding(scope, Match(clause->var, Var)->name, new(binding_t, .type=tag_type));
|
|
}
|
|
type_t *clause_type = get_type(scope, clause->body);
|
|
type_t *merged = type_or_type(overall_t, clause_type);
|
|
if (!merged)
|
|
code_err(clause->body, "The type of this branch is %T, which conflicts with the earlier branch type of %T",
|
|
clause_type, overall_t);
|
|
overall_t = merged;
|
|
}
|
|
|
|
if (when->else_body) {
|
|
bool any_unhandled = false;
|
|
for (match_t *m = matches; m; m = m->next) {
|
|
if (!m->handled) {
|
|
any_unhandled = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!any_unhandled)
|
|
code_err(when->else_body, "This 'else' block will never run because every tag is handled");
|
|
|
|
type_t *else_t = get_type(env, when->else_body);
|
|
type_t *merged = type_or_type(overall_t, else_t);
|
|
if (!merged)
|
|
code_err(when->else_body,
|
|
"I was expecting this block to have a %T value (based on earlier clauses), but it actually has a %T value.",
|
|
overall_t, else_t);
|
|
// return merged;
|
|
return Type(VoidType);
|
|
} else {
|
|
CORD unhandled = CORD_EMPTY;
|
|
for (match_t *m = matches; m; m = m->next) {
|
|
if (!m->handled)
|
|
unhandled = unhandled ? CORD_all(unhandled, ", ", m->name) : m->name;
|
|
}
|
|
if (unhandled)
|
|
code_err(ast, "This 'while' statement doesn't handle the tag(s): %s", CORD_to_const_char_star(unhandled));
|
|
// return overall_t;
|
|
return Type(VoidType);
|
|
}
|
|
}
|
|
|
|
case While: case For: return Type(VoidType);
|
|
|
|
case Unknown: code_err(ast, "I can't figure out the type of: %W", ast);
|
|
}
|
|
code_err(ast, "I can't figure out the type of: %s", ast_to_str(ast));
|
|
}
|
|
|
|
bool is_discardable(env_t *env, ast_t *ast)
|
|
{
|
|
switch (ast->tag) {
|
|
case UpdateAssign: case Assign: case Declare: case FunctionDef: case StructDef: case EnumDef: case Use:
|
|
return true;
|
|
default: break;
|
|
}
|
|
type_t *t = get_type(env, ast);
|
|
return (t->tag == VoidType || t->tag == AbortType);
|
|
}
|
|
|
|
|
|
type_t *get_namespace_type(env_t *env, ast_t *namespace_ast, type_t *type)
|
|
{
|
|
arg_t *ns_fields = NULL;
|
|
if (type) {
|
|
ns_fields = new(arg_t, .name="type", .type=Type(TypeInfoType), .next=ns_fields);
|
|
if (type->tag == EnumType) {
|
|
// Add enum constructors:
|
|
auto enum_ = Match(type, EnumType);
|
|
for (tag_t *tag = enum_->tags; tag; tag = tag->next) {
|
|
type_t *constructor_t = Type(FunctionType, .args=Match(tag->type, StructType)->fields,
|
|
.ret=type);
|
|
ns_fields = new(arg_t, .name=tag->name, .type=constructor_t, .next=ns_fields);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (ast_list_t *stmts = Match(namespace_ast, Block)->statements; stmts; stmts = stmts->next) {
|
|
ast_t *stmt = stmts->ast;
|
|
doctest_inner:
|
|
switch (stmt->tag) {
|
|
case Declare: {
|
|
auto decl = Match(stmt, Declare);
|
|
const char *name = Match(decl->var, Var)->name;
|
|
type_t *t = get_type(env, decl->value);
|
|
ns_fields = new(arg_t, .name=name, .type=t, .next=ns_fields);
|
|
break;
|
|
}
|
|
case FunctionDef: {
|
|
type_t *t = get_function_def_type(env, stmt);
|
|
const char *name = Match(Match(stmt, FunctionDef)->name, Var)->name;
|
|
ns_fields = new(arg_t, .name=name, .type=t, .next=ns_fields);
|
|
break;
|
|
}
|
|
case DocTest: {
|
|
stmt = Match(stmt, DocTest)->expr;
|
|
goto doctest_inner;
|
|
}
|
|
default: break;
|
|
}
|
|
}
|
|
return Type(StructType, .fields=ns_fields);
|
|
}
|
|
|
|
// typedef struct {
|
|
// file_t *file;
|
|
// env_t *env;
|
|
// ast_t *ast;
|
|
// } parsed_file_info_t;
|
|
|
|
// static parsed_file_info_t *get_file_info(env_t *env, const char *path)
|
|
// {
|
|
// static table_t cache = {0};
|
|
|
|
// struct stat file_stat;
|
|
// const char *sss_path = strlen(path) > 4 && streq(path + strlen(path) - 4, ".sss") ? path : heap_strf("%s.sss", path);
|
|
// if (stat(sss_path, &file_stat) == -1)
|
|
// compiler_err(NULL, NULL, NULL, "I can't find the file %s", sss_path);
|
|
|
|
// parsed_file_info_t *file_info = Table_str_get(&cache, path);
|
|
// if (file_info) return file_info;
|
|
|
|
// file_t *f = load_file(sss_path);
|
|
// file_info = new(parsed_file_info_t, .file=f);
|
|
// Table_str_set(&cache, path, file_info);
|
|
// file_info->env = new(env_t);
|
|
// *file_info->env = *env;
|
|
|
|
// file_info->ast = parse_file(f, NULL);
|
|
// table_t *type_bindings = new(table_t, .fallback=&env->global->types);
|
|
// bind_types(env, type_bindings, file_info->ast);
|
|
// populate_types(env, type_bindings, file_info->ast);
|
|
// bind_variables(env, new(table_t, .fallback=&env->global->bindings), file_info->ast);
|
|
// // type_t *ns_t = get_namespace_type(env, ast, NULL);
|
|
// return file_info;
|
|
// }
|
|
|
|
type_t *get_file_type(env_t *env, const char *path)
|
|
{
|
|
// auto info = get_file_info(env, path);
|
|
file_t *f = load_file(path);
|
|
ast_t *ast = parse_file(f, NULL);
|
|
if (!ast) compiler_err(NULL, NULL, NULL, "Couldn't parse file: %s", path);
|
|
return get_namespace_type(env, ast, NULL);
|
|
}
|
|
|
|
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|