diff --git a/releasenotes.md b/releasenotes.md index de88b86cf..84ac1b888 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -6,12 +6,13 @@ - `$typefrom` now also accepts a constant string, and so works like `$evaltype`. - `$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`. - Make $Type parameters accept constant typeid values. - Deprecate `foo.#bar`. - Allow inference across `&&` #2172. - Added support for custom file extensions in project.json targets. +- `$eval` now also works with `@foo`, `#foo`, `$Foo` and `$foo` parameters #2114. ### Fixes - `-2147483648`, MIN literals work correctly. diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index b6246c66f..e088a9fb9 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -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]; } -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; - 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; TokenType token = sema_splitpathref(inner->const_expr.bytes.ptr, inner->const_expr.bytes.len, &path, &interned_version); switch (token) @@ -356,6 +351,19 @@ Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bo case TOKEN_CONST_IDENT: inner->unresolved_ident_expr.is_const = true; 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: if (!interned_version) { @@ -369,12 +377,16 @@ Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bo break; case TYPE_TOKENS: { + if (path) goto NO_PATH; TypeInfo *info = type_info_new_base(type_from_token(token), inner->span); inner->expr_kind = EXPR_TYPEINFO; inner->resolve_status = RESOLVE_NOT_DONE; inner->type_expr = info; return inner; } + case TOKEN_CT_TYPE_IDENT: + if (path) goto NO_PATH; + FALLTHROUGH; case TOKEN_TYPE_IDENT: { 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; } default: - if (is_type_eval) - { - 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; + 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; } inner->expr_kind = EXPR_UNRESOLVED_IDENTIFIER; inner->resolve_status = RESOLVE_NOT_DONE; inner->unresolved_ident_expr.ident = interned_version; inner->unresolved_ident_expr.path = path; 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) @@ -4082,7 +4104,7 @@ RETRY: { case EXPR_HASH_IDENT: 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; goto RETRY; case EXPR_OTHER_CONTEXT: @@ -4109,6 +4131,7 @@ RETRY: ASSERT_SPAN(child, child->resolve_status != RESOLVE_DONE); // Only report missing if missing var is NULL Expr *result = sema_ct_eval_expr(context, false, child->inner_expr, missing == NULL); + if (!expr_ok(result)) return NULL; if (!result) { if (missing) *missing = true; @@ -9240,6 +9263,7 @@ RETRY: { Expr *expr = type_info->unresolved_type_expr; expr = sema_ct_eval_expr(context, true, expr, false); + if (!expr_ok(expr)) return poisoned_type; if (!expr) return NULL; if (expr->expr_kind != EXPR_TYPEINFO) { @@ -9691,8 +9715,12 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr break; } 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; + } case EXPR_HASH_IDENT: { 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 { + if (ch == '$' || ch == '@' || ch == '#') break; // $foo / @foo + 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; 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]; 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_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: - if (!*path_ref) return type; - FALLTHROUGH; + return type; default: *ident_ref = NULL; return TOKEN_INVALID_TOKEN; diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 999bbf408..e56ae7728 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -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); 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_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing); bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt); bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member); diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 287e9e642..7df929eb8 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -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."); Expr *expr = type_info->unresolved_type_expr; 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) { 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."); } - Path *path = NULL; - const char *interned_version = NULL; const char *bytes = expr->const_expr.bytes.ptr; - ArraySize bytes_len = expr->const_expr.bytes.len; - TokenType token = sema_splitpathref(bytes, bytes_len, &path, &interned_version); - TypeInfo *info; - switch (token) + ArraySize len = expr->const_expr.bytes.len; + Expr *typefrom = sema_resolve_string_ident(context, expr, true); + if (!typefrom || !expr_ok(typefrom)) return false; + if (typefrom->expr_kind != EXPR_TYPEINFO) { - case TOKEN_IDENT: - 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); + RETURN_SEMA_ERROR(expr, "Expected a type, not a regular identifier '%.*s'.", (int)len, bytes); } + TypeInfo *info = typefrom->type_expr; if (!sema_resolve_type(context, info, resolve_kind)) return false; 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; return true; 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: RETURN_SEMA_ERROR(expr, "$typefrom does not support compile-time types."); } diff --git a/test/test_suite/compile_time/ct_eval_wrong.c3 b/test/test_suite/compile_time/ct_eval_wrong.c3 index e6a11fdb5..dbe7f11f5 100644 --- a/test/test_suite/compile_time/ct_eval_wrong.c3 +++ b/test/test_suite/compile_time/ct_eval_wrong.c3 @@ -2,7 +2,7 @@ import std::io; fn void main() { - $eval("foo()"); // #error: with $eval + $eval("foo()"); // #error: could not be resolved to a valid } fn void foo() { diff --git a/test/test_suite/compile_time/extended_eval.c3t b/test/test_suite/compile_time/extended_eval.c3t new file mode 100644 index 000000000..7516dac61 --- /dev/null +++ b/test/test_suite/compile_time/extended_eval.c3t @@ -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 +} \ No newline at end of file diff --git a/test/test_suite/compile_time_introspection/nameof_err.c3 b/test/test_suite/compile_time_introspection/nameof_err.c3 index 685a364ad..fb09a4f53 100644 --- a/test/test_suite/compile_time_introspection/nameof_err.c3 +++ b/test/test_suite/compile_time_introspection/nameof_err.c3 @@ -16,5 +16,5 @@ fn void main3() fn void main4() { - $typefrom("foo::int").extnameof; // #error: Only valid types can be resolved with $typefrom + $typefrom("foo::int").extnameof; // #error: Unexpected path in } diff --git a/test/test_suite/compile_time_introspection/sizeof_errors.c3 b/test/test_suite/compile_time_introspection/sizeof_errors.c3 index d6b8a0cbd..06791b77f 100644 --- a/test/test_suite/compile_time_introspection/sizeof_errors.c3 +++ b/test/test_suite/compile_time_introspection/sizeof_errors.c3 @@ -24,7 +24,7 @@ fn void f() 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()