$eval now also works with @foo, #foo, $Foo and $foo parameters #2114.

This commit is contained in:
Christoffer Lerno
2025-06-06 01:23:23 +02:00
parent ef649050c4
commit 9baeca3a8e
8 changed files with 111 additions and 53 deletions

View File

@@ -6,12 +6,13 @@
- `$typefrom` now also accepts a constant string, and so works like `$evaltype`. - `$typefrom` now also accepts a constant string, and so works like `$evaltype`.
- `$evaltype` is deprecated in favour of `$typefrom`. - `$evaltype` is deprecated in favour of `$typefrom`.
- `-0xFF` will now be a signed integer. - Literal rules have changed, this makes `-0xFF` now a signed integer.
- Implicitly convert from constant typeid to Type in `$Type` assignment, and `$assignable`. - Implicitly convert from constant typeid to Type in `$Type` assignment, and `$assignable`.
- Make $Type parameters accept constant typeid values. - Make $Type parameters accept constant typeid values.
- Deprecate `foo.#bar`. - Deprecate `foo.#bar`.
- Allow inference across `&&` #2172. - Allow inference across `&&` #2172.
- Added support for custom file extensions in project.json targets. - Added support for custom file extensions in project.json targets.
- `$eval` now also works with `@foo`, `#foo`, `$Foo` and `$foo` parameters #2114.
### Fixes ### Fixes
- `-2147483648`, MIN literals work correctly. - `-2147483648`, MIN literals work correctly.

View File

@@ -340,15 +340,10 @@ Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, uns
return context->macro_varargs[(size_t)index_val.i.low]; return context->macro_varargs[(size_t)index_val.i.low];
} }
Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing) Expr *sema_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing)
{ {
ASSERT_SPAN(inner, expr_is_const_string(inner));
Path *path = NULL; Path *path = NULL;
if (!sema_analyse_ct_expr(context, inner)) return NULL;
if (!expr_is_const_string(inner))
{
SEMA_ERROR(inner, "'%s' expects a constant string as the argument.", is_type_eval ? "$evaltype" : "$eval");
return NULL;
}
const char *interned_version = NULL; const char *interned_version = NULL;
TokenType token = sema_splitpathref(inner->const_expr.bytes.ptr, inner->const_expr.bytes.len, &path, &interned_version); TokenType token = sema_splitpathref(inner->const_expr.bytes.ptr, inner->const_expr.bytes.len, &path, &interned_version);
switch (token) switch (token)
@@ -356,6 +351,19 @@ Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bo
case TOKEN_CONST_IDENT: case TOKEN_CONST_IDENT:
inner->unresolved_ident_expr.is_const = true; inner->unresolved_ident_expr.is_const = true;
break; break;
case TOKEN_HASH_IDENT:
if (path) goto NO_PATH;
inner->expr_kind = EXPR_HASH_IDENT;
inner->ct_ident_expr.identifier = interned_version;
inner->resolve_status = RESOLVE_NOT_DONE;
return inner;
case TOKEN_CT_IDENT:
if (path) goto NO_PATH;
inner->expr_kind = EXPR_CT_IDENT;
inner->ct_ident_expr.identifier = interned_version;
inner->resolve_status = RESOLVE_NOT_DONE;
return inner;
case TOKEN_AT_IDENT:
case TOKEN_IDENT: case TOKEN_IDENT:
if (!interned_version) if (!interned_version)
{ {
@@ -369,12 +377,16 @@ Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bo
break; break;
case TYPE_TOKENS: case TYPE_TOKENS:
{ {
if (path) goto NO_PATH;
TypeInfo *info = type_info_new_base(type_from_token(token), inner->span); TypeInfo *info = type_info_new_base(type_from_token(token), inner->span);
inner->expr_kind = EXPR_TYPEINFO; inner->expr_kind = EXPR_TYPEINFO;
inner->resolve_status = RESOLVE_NOT_DONE; inner->resolve_status = RESOLVE_NOT_DONE;
inner->type_expr = info; inner->type_expr = info;
return inner; return inner;
} }
case TOKEN_CT_TYPE_IDENT:
if (path) goto NO_PATH;
FALLTHROUGH;
case TOKEN_TYPE_IDENT: case TOKEN_TYPE_IDENT:
{ {
TypeInfo *info = type_info_new(TYPE_INFO_IDENTIFIER, inner->span); TypeInfo *info = type_info_new(TYPE_INFO_IDENTIFIER, inner->span);
@@ -387,21 +399,31 @@ Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bo
return inner; return inner;
} }
default: default:
if (is_type_eval) SEMA_ERROR(inner, "'%.*s' could not be resolved to a valid symbol.", (int)inner->const_expr.bytes.len, inner->const_expr.bytes.ptr);
{ return poisoned_expr;
SEMA_ERROR(inner, "Only valid types may be resolved with $evaltype.");
}
else
{
SEMA_ERROR(inner, "Only plain function, variable and constant names may be resolved with $eval.");
}
return NULL;
} }
inner->expr_kind = EXPR_UNRESOLVED_IDENTIFIER; inner->expr_kind = EXPR_UNRESOLVED_IDENTIFIER;
inner->resolve_status = RESOLVE_NOT_DONE; inner->resolve_status = RESOLVE_NOT_DONE;
inner->unresolved_ident_expr.ident = interned_version; inner->unresolved_ident_expr.ident = interned_version;
inner->unresolved_ident_expr.path = path; inner->unresolved_ident_expr.path = path;
return inner; return inner;
NO_PATH:
if (report_missing)
{
SEMA_ERROR(inner, "Unexpected path in '%.*s'.", (int)inner->const_expr.bytes.len, inner->const_expr.bytes.ptr);
}
return NULL;
}
Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing)
{
if (!sema_analyse_ct_expr(context, inner)) return NULL;
if (!expr_is_const_string(inner))
{
SEMA_ERROR(inner, "'%s' expects a constant string as the argument.", is_type_eval ? "$evaltype" : "$eval");
return poisoned_expr;
}
return sema_resolve_string_ident(context, inner, report_missing);
} }
Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl) Expr *expr_access_inline_member(Expr *parent, Decl *parent_decl)
@@ -4082,7 +4104,7 @@ RETRY:
{ {
case EXPR_HASH_IDENT: case EXPR_HASH_IDENT:
SEMA_DEPRECATED(child, "Using 'abc.#foo' access style is deprecated. Use 'abc.eval($foo)' instead."); SEMA_DEPRECATED(child, "Using 'abc.#foo' access style is deprecated. Use 'abc.eval($foo)' instead.");
if (!sema_expr_fold_hash(context, child)) return false; if (!sema_expr_fold_hash(context, child)) return NULL;
in_hash = true; in_hash = true;
goto RETRY; goto RETRY;
case EXPR_OTHER_CONTEXT: case EXPR_OTHER_CONTEXT:
@@ -4109,6 +4131,7 @@ RETRY:
ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE); ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE);
// Only report missing if missing var is NULL // Only report missing if missing var is NULL
Expr *result = sema_ct_eval_expr(context, false, child->inner_expr, missing == NULL); Expr *result = sema_ct_eval_expr(context, false, child->inner_expr, missing == NULL);
if (!expr_ok(result)) return NULL;
if (!result) if (!result)
{ {
if (missing) *missing = true; if (missing) *missing = true;
@@ -9240,6 +9263,7 @@ RETRY:
{ {
Expr *expr = type_info->unresolved_type_expr; Expr *expr = type_info->unresolved_type_expr;
expr = sema_ct_eval_expr(context, true, expr, false); expr = sema_ct_eval_expr(context, true, expr, false);
if (!expr_ok(expr)) return poisoned_type;
if (!expr) return NULL; if (!expr) return NULL;
if (expr->expr_kind != EXPR_TYPEINFO) if (expr->expr_kind != EXPR_TYPEINFO)
{ {
@@ -9691,8 +9715,12 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr
break; break;
} }
case EXPR_CT_EVAL: case EXPR_CT_EVAL:
success = sema_ct_eval_expr(active_context, "$eval", main_expr->inner_expr, false); {
Expr *eval = sema_ct_eval_expr(active_context, "$eval", main_expr->inner_expr, false);
if (!expr_ok(eval)) return false;
success = eval != NULL;
break; break;
}
case EXPR_HASH_IDENT: case EXPR_HASH_IDENT:
{ {
Decl *decl = sema_resolve_symbol(active_context, main_expr->hash_ident_expr.identifier, NULL, main_expr->span); Decl *decl = sema_resolve_symbol(active_context, main_expr->hash_ident_expr.identifier, NULL, main_expr->span);
@@ -10997,6 +11025,8 @@ TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref,
} }
else else
{ {
if (ch == '$' || ch == '@' || ch == '#') break; // $foo / @foo
return TOKEN_INVALID_TOKEN; return TOKEN_INVALID_TOKEN;
} }
} }
@@ -11016,7 +11046,20 @@ TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref,
} }
if (len == 0) return TOKEN_INVALID_TOKEN; if (len == 0) return TOKEN_INVALID_TOKEN;
uint32_t hash = FNV1_SEED; uint32_t hash = FNV1_SEED;
for (size_t i = 0; i < len; i++) size_t start = 0;
switch (string[0])
{
case '@':
case '$':
case '#':
hash = FNV1a(string[0], hash);
start = 1;
break;
default:
break;
}
for (size_t i = start; i < len; i++)
{ {
char c = string[i]; char c = string[i];
if (!char_is_alphanum_(c)) return TOKEN_INVALID_TOKEN; if (!char_is_alphanum_(c)) return TOKEN_INVALID_TOKEN;
@@ -11030,10 +11073,12 @@ TokenType sema_splitpathref(const char *string, ArraySize len, Path **path_ref,
case TOKEN_TYPE_IDENT: case TOKEN_TYPE_IDENT:
case TOKEN_IDENT: case TOKEN_IDENT:
case TOKEN_CONST_IDENT: case TOKEN_CONST_IDENT:
return type; case TOKEN_AT_IDENT:
case TOKEN_CT_TYPE_IDENT:
case TOKEN_CT_IDENT:
case TOKEN_HASH_IDENT:
case TYPE_TOKENS: case TYPE_TOKENS:
if (!*path_ref) return type; return type;
FALLTHROUGH;
default: default:
*ident_ref = NULL; *ident_ref = NULL;
return TOKEN_INVALID_TOKEN; return TOKEN_INVALID_TOKEN;

View File

@@ -102,6 +102,7 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr);
bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *struct_var, Decl *decl, bool call_var_optional, bool *no_match_ref); bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *struct_var, Decl *decl, bool call_var_optional, bool *no_match_ref);
Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, unsigned *index_ref); Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, unsigned *index_ref);
Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing); Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing);
Expr *sema_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing);
bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt); bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt);
bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member); bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member);

View File

@@ -296,7 +296,7 @@ INLINE bool sema_resolve_evaltype(SemaContext *context, TypeInfo *type_info, Res
SEMA_DEPRECATED(type_info, "$evaltype is deprecated, use $typefrom instead."); SEMA_DEPRECATED(type_info, "$evaltype is deprecated, use $typefrom instead.");
Expr *expr = type_info->unresolved_type_expr; Expr *expr = type_info->unresolved_type_expr;
Expr *inner = sema_ct_eval_expr(context, true, expr, true); Expr *inner = sema_ct_eval_expr(context, true, expr, true);
if (!inner) return type_info_poison(type_info); if (!inner || !expr_ok(inner)) return type_info_poison(type_info);
if (inner->expr_kind != EXPR_TYPEINFO) if (inner->expr_kind != EXPR_TYPEINFO)
{ {
SEMA_ERROR(expr, "Only type names may be resolved with $evaltype."); SEMA_ERROR(expr, "Only type names may be resolved with $evaltype.");
@@ -373,34 +373,15 @@ INLINE bool sema_resolve_typefrom(SemaContext *context, TypeInfo *type_info, Res
RETURN_SEMA_ERROR(expr, "Expected a constant string or typeid value."); RETURN_SEMA_ERROR(expr, "Expected a constant string or typeid value.");
} }
Path *path = NULL;
const char *interned_version = NULL;
const char *bytes = expr->const_expr.bytes.ptr; const char *bytes = expr->const_expr.bytes.ptr;
ArraySize bytes_len = expr->const_expr.bytes.len; ArraySize len = expr->const_expr.bytes.len;
TokenType token = sema_splitpathref(bytes, bytes_len, &path, &interned_version); Expr *typefrom = sema_resolve_string_ident(context, expr, true);
TypeInfo *info; if (!typefrom || !expr_ok(typefrom)) return false;
switch (token) if (typefrom->expr_kind != EXPR_TYPEINFO)
{ {
case TOKEN_IDENT: RETURN_SEMA_ERROR(expr, "Expected a type, not a regular identifier '%.*s'.", (int)len, bytes);
if (slice_is_type(bytes, bytes_len))
{
RETURN_SEMA_ERROR(expr, "'%.*s' could not be found, did you spell it right?", (int)bytes_len, bytes);
}
FALLTHROUGH;
case TOKEN_CONST_IDENT:
RETURN_SEMA_ERROR(expr, "Expected a type name, but the argument was \"%.*s\".", (int)bytes_len, bytes);
case TYPE_TOKENS:
type_info->type = type_from_token(token);
return true;
case TOKEN_TYPE_IDENT:
info = type_info_new(TYPE_INFO_IDENTIFIER, expr->span);
info->unresolved.name = interned_version;
info->unresolved.path = path;
info->resolve_status = RESOLVE_NOT_DONE;
break;
default:
RETURN_SEMA_ERROR(expr, "Only valid types can be resolved with $typefrom. You passed the string \"%.*s\", which cannot be resolved as a type.", (int)bytes_len, bytes);
} }
TypeInfo *info = typefrom->type_expr;
if (!sema_resolve_type(context, info, resolve_kind)) return false; if (!sema_resolve_type(context, info, resolve_kind)) return false;
switch (sema_resolve_storage_type(context, info->type)) switch (sema_resolve_storage_type(context, info->type))
{ {
@@ -412,7 +393,7 @@ INLINE bool sema_resolve_typefrom(SemaContext *context, TypeInfo *type_info, Res
type_info->type = info->type; type_info->type = info->type;
return true; return true;
case STORAGE_WILDCARD: case STORAGE_WILDCARD:
RETURN_SEMA_ERROR(expr, "$typefrom failed to resolve \"%.*s\" to a definite type.", (int)bytes_len, bytes); RETURN_SEMA_ERROR(expr, "$typefrom failed to resolve \"%.*s\" to a definite type.", (int)len, bytes);
case STORAGE_COMPILE_TIME: case STORAGE_COMPILE_TIME:
RETURN_SEMA_ERROR(expr, "$typefrom does not support compile-time types."); RETURN_SEMA_ERROR(expr, "$typefrom does not support compile-time types.");
} }

View File

@@ -2,7 +2,7 @@ import std::io;
fn void main() fn void main()
{ {
$eval("foo()"); // #error: with $eval $eval("foo()"); // #error: could not be resolved to a valid
} }
fn void foo() fn void foo()
{ {

View File

@@ -0,0 +1,30 @@
// #target: macos-x64
module test;
macro @abc(#a)
{
$eval("#a") = 1;
}
fn void main()
{
int $foo = 1;
$eval("$foo") = 2;
$typefrom("int") x;
var $Type = double;
$typefrom("$Type") y;
$eval("@abc")(x);
x = 0;
$eval("test::@abc")(x);
}
/* #expect: test.ll
entry:
%x = alloca i32, align 4
%y = alloca double, align 8
store i32 0, ptr %x, align 4
store double 0.000000e+00, ptr %y, align 8
store i32 1, ptr %x, align 4
store i32 0, ptr %x, align 4
store i32 1, ptr %x, align 4
ret void
}

View File

@@ -16,5 +16,5 @@ fn void main3()
fn void main4() fn void main4()
{ {
$typefrom("foo::int").extnameof; // #error: Only valid types can be resolved with $typefrom $typefrom("foo::int").extnameof; // #error: Unexpected path in
} }

View File

@@ -24,7 +24,7 @@ fn void f()
fn void g() fn void g()
{ {
int x = $typefrom("bar::").sizeof; // #error: Only valid types can be resolved with $typefrom int x = $typefrom("bar::").sizeof; // #error: could not be resolved to a valid symbol
} }
fn void k() fn void k()