From 5d9a7ab0a63a10e028ea12f3aa86a7dfbc2bf65b Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 13 Oct 2022 09:26:15 +0200 Subject: [PATCH] Extend "var" to allow type inference on variables. --- lib/std/core/builtin.c3 | 18 ++++---- lib/std/core/builtin_comparison.c3 | 4 +- src/compiler/compiler_internal.h | 16 +++++++ src/compiler/parse_global.c | 14 ++++++ src/compiler/sema_decls.c | 48 ++++++++++++++------ src/version.h | 2 +- test/test_suite/assignment/var_variable.c3t | 40 ++++++++++++++++ test/test_suite2/assignment/var_variable.c3t | 40 ++++++++++++++++ 8 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 test/test_suite/assignment/var_variable.c3t create mode 100644 test/test_suite2/assignment/var_variable.c3t diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index 5ce0c42e0..78d4bf0ed 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -28,16 +28,16 @@ fault VarCastResult **/ macro void @scope(&variable; @body) @builtin { - $typeof(variable) temp = variable; + var temp = variable; defer variable = temp; @body(); } macro void @swap(&a, &b) @builtin { - $typeof(a) temp = a; - a = b; - b = temp; + var temp = a; + a = b; + b = temp; } /** @@ -95,11 +95,11 @@ macro void unreachable($string = "Unreachable statement reached.") @builtin @nor macro bitcast(expr, $Type) @builtin { - var $size = (usz)($sizeof(expr)); - $assert($size == $Type.sizeof, "Cannot bitcast between types of different size."); - $Type x = void; - mem::copy(&x, &expr, $size, $Type.alignof, $alignof(expr)); - return x; + var $size = (usz)($sizeof(expr)); + $assert($size == $Type.sizeof, "Cannot bitcast between types of different size."); + $Type x = void; + mem::copy(&x, &expr, $size, $Type.alignof, $alignof(expr)); + return x; } /** diff --git a/lib/std/core/builtin_comparison.c3 b/lib/std/core/builtin_comparison.c3 index ec90dde36..7f1fba59c 100644 --- a/lib/std/core/builtin_comparison.c3 +++ b/lib/std/core/builtin_comparison.c3 @@ -80,7 +80,7 @@ macro min(x, ...) @builtin $if ($vacount == 1): return less(x, $vaarg(0)) ? x : $vaarg(0); $else: - $typeof(x) result = x; + var result = x; $for (var $i = 0; $i < $vacount; $i++): if (less($vaarg($i), result)) { @@ -96,7 +96,7 @@ macro max(x, ...) @builtin $if ($vacount == 1): return greater(x, $vaarg(0)) ? x : $vaarg(0); $else: - $typeof(x) result = x; + var result = x; $for (var $i = 0; $i < $vacount; $i++): if (greater($vaarg($i), result)) { diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 40731bbd6..5a0a9b4cd 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2309,6 +2309,7 @@ INLINE Type *type_from_inferred(Type *flattened, Type *element_type, unsigned co INLINE bool type_len_is_inferred(Type *type); INLINE bool type_is_substruct(Type *type); INLINE Type *type_flatten_for_bitstruct(Type *type); +INLINE const char *type_invalid_storage_type_name(Type *type); INLINE bool type_is_float(Type *type); INLINE bool type_is_optional(Type *type); INLINE bool type_is_optional_type(Type *type); @@ -2559,6 +2560,21 @@ INLINE bool type_is_float(Type *type) return kind >= TYPE_FLOAT_FIRST && kind <= TYPE_FLOAT_LAST; } +INLINE const char *type_invalid_storage_type_name(Type *type) +{ + switch (type->type_kind) + { + case TYPE_MEMBER: + return "a member reference"; + case TYPE_UNTYPED_LIST: + return "an untyped list"; + case TYPE_TYPEINFO: + return "a type"; + default: + UNREACHABLE; + } +} + INLINE bool type_is_invalid_storage_type(Type *type) { if (!type) return false; diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index b659124f9..64ab28431 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -860,6 +860,20 @@ Decl *parse_var_decl(ParseContext *c) Decl *decl; switch (c->tok) { + case TOKEN_CONST_IDENT: + SEMA_ERROR_HERE("Constants must be declared using 'const' not 'var'."); + return poisoned_decl; + case TOKEN_IDENT: + decl = DECL_VAR_NEW(NULL, VARDECL_LOCAL, VISIBLE_LOCAL); + advance(c); + if (!tok_is(c, TOKEN_EQ)) + { + SEMA_ERROR_HERE("'var' must always have an initial value, or the type cannot be inferred."); + return false; + } + advance_and_verify(c, TOKEN_EQ); + ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl); + break; case TOKEN_CT_IDENT: decl = DECL_VAR_NEW(NULL, VARDECL_LOCAL_CT, VISIBLE_LOCAL); advance(c); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 0cecae814..1de653221 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -2341,11 +2341,11 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) // We expect a constant to actually be parsed correctly so that it has a value, so // this should always be true. - assert(decl->var.type_info || decl->var.kind == VARDECL_CONST); + assert(decl->var.type_info || decl->var.init_expr); bool is_global = decl->var.kind == VARDECL_GLOBAL || !local; - if (!sema_analyse_attributes_for_var(context, decl)) return false; + if (!sema_analyse_attributes_for_var(context, decl)) return decl_poison(decl); if (is_global) { @@ -2358,42 +2358,59 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) if (context->current_macro) { SEMA_ERROR(decl, "Macros with declarations may not be used outside of functions."); - return false; + return decl_poison(decl); } SEMA_ERROR(decl, "Variable declarations may not be used outside of functions."); - return false; + return decl_poison(decl); } // Add a local to the current context, will throw error on shadowing. if (!sema_add_local(context, decl)) return decl_poison(decl); } // 1. Local or global constants: const int FOO = 123. - if (decl->var.kind == VARDECL_CONST) + if (!decl->var.type_info) { Expr *init_expr = decl->var.init_expr; // 1a. We require an init expression. if (!init_expr) { + assert(decl->var.kind == VARDECL_CONST); SEMA_ERROR(decl, "Constants need to have an initial value."); - return false; + return decl_poison(decl); + } + if (decl->var.kind == VARDECL_LOCAL && !context->current_macro) + { + SEMA_ERROR(decl, "Defining a variable using 'var %s = ...' is only allowed inside a macro.", decl->name); + return decl_poison(decl); } assert(!decl->var.no_init); if (!decl->var.type_info) { - if (!sema_analyse_expr(context, init_expr)) return false; + if (!sema_analyse_expr(context, init_expr)) return decl_poison(decl); if (is_global && !expr_is_constant_eval(init_expr, CONSTANT_EVAL_GLOBAL_INIT)) { SEMA_ERROR(init_expr, "This expression cannot be evaluated at compile time."); - return false; + return decl_poison(decl); } decl->type = init_expr->type; if (type_is_invalid_storage_type(init_expr->type)) { - SEMA_ERROR(init_expr, "A value of type '%s' cannot be used as a constant.", type_quoted_error_string(init_expr->type)); - return false; + if (init_expr->type == type_untypedlist) + { + SEMA_ERROR(init_expr, "The type of an untyped list cannot be inferred, you can try adding an explicit type to solve this."); + } + else if (decl->var.kind == VARDECL_CONST) + { + SEMA_ERROR(init_expr, "You cannot initialize a constant to %s, but you can assign the expression to a compile time variable.", type_invalid_storage_type_name(init_expr->type)); + } + else + { + SEMA_ERROR(init_expr, "You can't store a compile time type in a variable."); + } + return decl_poison(decl); } if (!decl->alignment) decl->alignment = type_alloca_alignment(decl->type); - if (!sema_analyse_decl_type(context, decl->type, init_expr->span)) return false; + if (!sema_analyse_decl_type(context, decl->type, init_expr->span)) return decl_poison(decl); // Skip further evaluation. goto EXIT_OK; } @@ -2402,12 +2419,12 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) if (!sema_resolve_type_info_maybe_inferred(context, decl->var.type_info, decl->var.init_expr != NULL)) return decl_poison(decl); decl->type = decl->var.type_info->type; - if (!sema_analyse_decl_type(context, decl->type, decl->var.type_info->span)) return false; + if (!sema_analyse_decl_type(context, decl->type, decl->var.type_info->span)) return decl_poison(decl); bool is_static = decl->var.is_static; if (is_static && context->call_env.pure) { SEMA_ERROR(decl, "'@pure' functions may not have static variables."); - return false; + return decl_poison(decl); } if (is_static && !decl->has_extname) { @@ -2422,7 +2439,7 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) if (!decl->var.init_expr && infer_len) { SEMA_ERROR(decl->var.type_info, "The length cannot be inferred without an initializer."); - return false; + return decl_poison(decl); } if (decl->var.init_expr) { @@ -2449,7 +2466,7 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) if (type_is_len_inferred(init->type)) { SEMA_ERROR(decl->var.type_info, "You cannot use [*] and [<*>] underlying types with initializers."); - return false; + return decl_poison(decl); } decl->type = cast_infer_len(decl->type, init->type); } @@ -2460,6 +2477,7 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local) if ((is_global || decl->var.is_static) && !expr_is_constant_eval(init_expr, CONSTANT_EVAL_GLOBAL_INIT)) { SEMA_ERROR(init_expr, "The expression must be a constant value."); + return decl_poison(decl); } else { diff --git a/src/version.h b/src/version.h index bc36943ac..1fb56b042 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.3.82" \ No newline at end of file +#define COMPILER_VERSION "0.3.83" \ No newline at end of file diff --git a/test/test_suite/assignment/var_variable.c3t b/test/test_suite/assignment/var_variable.c3t new file mode 100644 index 000000000..1eed2f9b0 --- /dev/null +++ b/test/test_suite/assignment/var_variable.c3t @@ -0,0 +1,40 @@ +module test; +import std::io; + + +macro @foo(;@body) +{ + var i = 1.0; + @body(); +} + +fn void main() +{ + @foo() { int j = 1; }; + @foo() { var j = 1.0; }; // #error: is only allowed inside +} + +fn void test() +{ + var g = 1; // #error: is only allowed inside +} + +macro void test2m() +{ + var h = { 1, 2 }; // #error: cannot be inferred +} + +fn void test2() +{ + test2m(); +} +struct Foo { int a; } +macro void test3m() +{ + var $foo = Foo.membersof; + var h = $foo[0]; // #error: compile time type +} +fn void test3() +{ + test3m(); +} \ No newline at end of file diff --git a/test/test_suite2/assignment/var_variable.c3t b/test/test_suite2/assignment/var_variable.c3t new file mode 100644 index 000000000..1eed2f9b0 --- /dev/null +++ b/test/test_suite2/assignment/var_variable.c3t @@ -0,0 +1,40 @@ +module test; +import std::io; + + +macro @foo(;@body) +{ + var i = 1.0; + @body(); +} + +fn void main() +{ + @foo() { int j = 1; }; + @foo() { var j = 1.0; }; // #error: is only allowed inside +} + +fn void test() +{ + var g = 1; // #error: is only allowed inside +} + +macro void test2m() +{ + var h = { 1, 2 }; // #error: cannot be inferred +} + +fn void test2() +{ + test2m(); +} +struct Foo { int a; } +macro void test3m() +{ + var $foo = Foo.membersof; + var h = $foo[0]; // #error: compile time type +} +fn void test3() +{ + test3m(); +} \ No newline at end of file