Add @safeinfer to allow var to be used locally.

This commit is contained in:
Christoffer Lerno
2025-08-21 12:39:08 +02:00
parent 6ab7198f2f
commit e2e2ca1d7f
8 changed files with 29 additions and 12 deletions

View File

@@ -18,6 +18,7 @@
- Improve error message for missing `$endif`.
- `foo[x][y] = b` now interpreted as `(*&foo[x])[y] = b` which allows overloads to do chained [] accesses.
- Error if a stack allocated variable is too big (configurable with `--max-stack-object-size`).
- Add `@safeinfer` to allow `var` to be used locally.
### Fixes
- List.remove_at would incorrectly trigger ASAN.

View File

@@ -466,6 +466,7 @@ typedef struct VarDecl_
bool is_temp : 1;
bool copy_const : 1;
bool defaulted : 1;
bool safe_infer : 1;
union
{
Expr *init_expr;

View File

@@ -307,6 +307,7 @@ typedef enum
ATTRIBUTE_PUBLIC,
ATTRIBUTE_PURE,
ATTRIBUTE_REFLECT,
ATTRIBUTE_SAFEINFER,
ATTRIBUTE_SAFEMACRO,
ATTRIBUTE_SECTION,
ATTRIBUTE_STRUCTLIKE,

View File

@@ -857,7 +857,7 @@ Decl *parse_local_decl_after_type(ParseContext *c, TypeInfo *type)
bool is_cond;
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
decl->is_cond = true;
decl->is_cond = is_cond;
if (tok_is(c, TOKEN_EQ))
{
if (!decl)
@@ -949,6 +949,8 @@ Decl *parse_var_decl(ParseContext *c)
// analyser. The runtime variables must have an initializer unlike the CT ones.
advance_and_verify(c, TOKEN_VAR);
Decl *decl;
bool is_cond;
SourceSpan span;
switch (c->tok)
{
case TOKEN_CONST_IDENT:
@@ -957,6 +959,8 @@ Decl *parse_var_decl(ParseContext *c)
case TOKEN_IDENT:
decl = decl_new_var_current(c, NULL, VARDECL_LOCAL);
advance(c);
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
decl->is_cond = is_cond;
if (!tok_is(c, TOKEN_EQ))
{
PRINT_ERROR_HERE("'var' must always have an initial value, or the type cannot be inferred.");
@@ -966,16 +970,16 @@ Decl *parse_var_decl(ParseContext *c)
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl);
break;
case TOKEN_CT_IDENT:
decl = decl_new_var_current(c, NULL, VARDECL_LOCAL_CT);
advance(c);
if (try_consume(c, TOKEN_EQ))
{
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl);
}
break;
case TOKEN_CT_TYPE_IDENT:
decl = decl_new_var_current(c, NULL, VARDECL_LOCAL_CT_TYPE);
decl = decl_new_var_current(c, NULL, c->tok == TOKEN_CT_IDENT ? VARDECL_LOCAL_CT : VARDECL_LOCAL_CT_TYPE);
advance(c);
span = c->span;
if (!parse_attributes(c, &decl->attributes, NULL, NULL, &is_cond)) return poisoned_decl;
if (is_cond || decl->attributes)
{
print_error_at(span, "Attributes are not allowed on compile time variables.");
return poisoned_decl;
}
if (try_consume(c, TOKEN_EQ))
{
ASSIGN_EXPR_OR_RET(decl->var.init_expr, parse_expr(c), poisoned_decl);

View File

@@ -3055,6 +3055,7 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_
[ATTRIBUTE_PURE] = ATTR_CALL,
[ATTRIBUTE_REFLECT] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES,
[ATTRIBUTE_SAFEMACRO] = ATTR_MACRO,
[ATTRIBUTE_SAFEINFER] = ATTR_GLOBAL | ATTR_LOCAL,
[ATTRIBUTE_SECTION] = ATTR_FUNC | ATTR_CONST | ATTR_GLOBAL,
[ATTRIBUTE_STRUCTLIKE] = ATTR_DISTINCT,
[ATTRIBUTE_TAG] = ATTR_BITSTRUCT_MEMBER | ATTR_MEMBER | USER_DEFINED_TYPES | CALLABLE_TYPE,
@@ -3421,6 +3422,9 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_
decl->attr_nopadding = true;
decl->attr_compact = true;
break;
case ATTRIBUTE_SAFEINFER:
decl->var.safe_infer = true;
break;
case ATTRIBUTE_NOINIT:
decl->var.no_init = true;
break;
@@ -4651,9 +4655,9 @@ bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local)
return decl_poison(decl);
}
ASSERT(!decl->var.no_init);
if (kind == VARDECL_LOCAL && !context_is_macro(context) && init_expr->expr_kind != EXPR_LAMBDA)
if (kind == VARDECL_LOCAL && !context_is_macro(context) && init_expr->expr_kind != EXPR_LAMBDA && !decl->var.safe_infer)
{
SEMA_ERROR(decl, "Defining a variable using 'var %s = ...' is only allowed inside a macro, or when defining a lambda.", decl->name);
SEMA_ERROR(decl, "Defining a variable using 'var %s = ...' is only allowed inside a macro, or when defining a lambda. You can override this by adding the attribute '@safeinfer' to the declaration.", decl->name);
return decl_poison(decl);
}
if (!sema_analyse_expr(context, init_expr)) return decl_poison(decl);

View File

@@ -372,6 +372,7 @@ void symtab_init(uint32_t capacity)
attribute_list[ATTRIBUTE_PURE] = kw_at_pure;
attribute_list[ATTRIBUTE_PUBLIC] = KW_DEF("@public");
attribute_list[ATTRIBUTE_REFLECT] = KW_DEF("@reflect");
attribute_list[ATTRIBUTE_SAFEINFER] = KW_DEF("@safeinfer");
attribute_list[ATTRIBUTE_SAFEMACRO] = KW_DEF("@safemacro");
attribute_list[ATTRIBUTE_SECTION] = KW_DEF("@section");
attribute_list[ATTRIBUTE_STRUCTLIKE] = KW_DEF("@structlike");

View File

@@ -0,0 +1,5 @@
fn int main(String[] args)
{
var y @safeinfer = 1;
return 0;
}

View File

@@ -1,6 +1,6 @@
import std;
fn int main(String[] args)
{
int[1024 * 1024] x; // #error: The size of this local variable (4096 Kb) exceeds the maximum allowed stack object size (128 Kb)
int[1024 * 1024] x; // #error: The size of this local variable (4096 Kb) exceeds the maximum allowed
return 0;
}