Make methods be available in earlier stages of analysis. Add @adhoc attribute to allow types with ad hoc generic declarations.

This commit is contained in:
Christoffer Lerno
2024-09-25 14:26:49 +02:00
parent 6f7ffbeb3c
commit da47588502
14 changed files with 150 additions and 66 deletions

View File

@@ -1,6 +1,6 @@
module std::collections::maybe(<Type>);
struct Maybe
struct Maybe @adhoc
{
Type value;
bool has_value;

View File

@@ -1,6 +1,6 @@
module std::collections::tuple(<Type1, Type2>);
struct Tuple @deprecated
struct Tuple @adhoc
{
Type1 first;
Type2 second;
@@ -8,7 +8,7 @@ struct Tuple @deprecated
module std::collections::triple(<Type1, Type2, Type3>);
struct Triple @deprecated
struct Triple @adhoc
{
Type1 first;
Type2 second;

View File

@@ -23,11 +23,12 @@
- Allow the "self" parameter to be $/# for macro methods.
- Support compile time slicing of untyped lists.
- Allow specifying an import module using `@wasm` #1305.
- Deprecated inline generic types outside of struct definitions and macros unless marked `@adhoc`.
- Improved method detection in earlier stages of checking.
### Fixes
- Issue where a lambda wasn't correctly registered as external. #1408
- Generic methods were incorrectly registered as functions, leading to naming collisions. #1402
- Deprecated inline generic types outside of struct definitions and macros.
- Deprecated tuple / triple types.
- Converting a slice to a vector/array would copy too little data.
- Crash when reading an empty 'manifest.json'.

View File

@@ -600,6 +600,7 @@ typedef struct Decl_
bool no_strip : 1;
bool is_cond : 1;
bool is_if : 1;
bool is_adhoc : 1;
bool attr_nopadding : 1;
bool attr_compact : 1;
bool resolved_attributes : 1;
@@ -1604,6 +1605,7 @@ struct CompilationUnit_
Decl **ct_includes;
Decl **vars;
Decl **macros;
Decl **methods_to_register;
Decl **methods;
Decl **macro_methods;
Decl **global_decls;
@@ -2273,6 +2275,7 @@ bool sema_cast_const(Expr *expr);
bool sema_expr_check_discard(SemaContext *context, Expr *expr);
bool sema_analyse_inferred_expr(SemaContext *context, Type *to, Expr *expr);
bool sema_analyse_decl(SemaContext *context, Decl *decl);
bool sema_analyse_method_register(SemaContext *context, Decl *method);
bool sema_resolve_type_structure(SemaContext *context, Type *type, SourceSpan span);
bool sema_analyse_var_decl_ct(SemaContext *context, Decl *decl);
bool sema_analyse_var_decl(SemaContext *context, Decl *decl, bool local);

View File

@@ -181,7 +181,7 @@ void unit_register_global_decl(CompilationUnit *unit, Decl *decl)
assert(decl->name);
if (decl->func_decl.type_parent)
{
vec_add(unit->macro_methods, decl);
vec_add(unit->methods_to_register, decl);
return;
}
else
@@ -194,7 +194,7 @@ void unit_register_global_decl(CompilationUnit *unit, Decl *decl)
assert(decl->name);
if (decl->func_decl.type_parent)
{
vec_add(unit->methods, decl);
vec_add(unit->methods_to_register, decl);
return;
}
else
@@ -233,7 +233,6 @@ void unit_register_global_decl(CompilationUnit *unit, Decl *decl)
vec_add(unit->attributes, decl);
decl_register(decl);
break;
case DECL_FAULTVALUE:
case DECL_ENUM_CONSTANT:
case DECL_IMPORT:

View File

@@ -46,9 +46,13 @@ typedef enum
ANALYSIS_MODULE_TOP,
ANALYSIS_IMPORTS,
ANALYSIS_REGISTER_GLOBAL_DECLARATIONS,
ANALYSIS_METHODS_REGISTER,
ANALYSIS_INCLUDES,
ANALYSIS_METHODS_INCLUDES,
ANALYSIS_REGISTER_CONDITIONAL_UNITS,
ANALYSIS_REGISTER_CONDITIONAL_DECLARATIONS,
ANALYSIS_METHODS_CONDITIONAL,
ANALYSIS_POST_REGISTER,
ANALYSIS_DECLS,
ANALYSIS_CT_ECHO,
ANALYSIS_CT_ASSERT,
@@ -250,6 +254,7 @@ typedef enum FLAG_ATTR
typedef enum
{
ATTRIBUTE_ADHOC,
ATTRIBUTE_ALIGN,
ATTRIBUTE_BENCHMARK,
ATTRIBUTE_BIGENDIAN,
@@ -988,10 +993,11 @@ typedef enum
typedef enum
{
RESOLVE_TYPE_DEFAULT,
RESOLVE_TYPE_ALLOW_INFER = 0x01,
RESOLVE_TYPE_ALLOW_FLEXIBLE = 0x02,
RESOLVE_TYPE_MACRO_METHOD = RESOLVE_TYPE_ALLOW_INFER,
RESOLVE_TYPE_FUNC_METHOD = RESOLVE_TYPE_DEFAULT
RESOLVE_TYPE_ALLOW_INFER = 0x01,
RESOLVE_TYPE_ALLOW_FLEXIBLE = 0x02,
RESOLVE_TYPE_NO_CHECK_DISTINCT = 0x04,
RESOLVE_TYPE_MACRO_METHOD = RESOLVE_TYPE_ALLOW_INFER | RESOLVE_TYPE_NO_CHECK_DISTINCT,
RESOLVE_TYPE_FUNC_METHOD = RESOLVE_TYPE_NO_CHECK_DISTINCT,
} ResolveTypeKind;
typedef enum FLAG_ATTR

View File

@@ -1780,15 +1780,6 @@ INLINE SourceSpan method_find_overload_span(Decl *method)
static inline bool unit_add_base_extension_method(SemaContext *context, CompilationUnit *unit, Type *parent_type, Decl *method)
{
// We don't support operator overloading on base types, because
// there seems little use for it frankly.
if (method->operator)
{
sema_error_at(context, method_find_overload_span(method),
"Only user-defined types support operator oveloading.");
return false;
}
// Add it to the right list of extensions.
switch (method->visibility)
{
@@ -1841,10 +1832,20 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type
// Check it's valid for the operator type.
if (!sema_check_operator_method_validity(context, method)) return false;
// We don't support operator overloading on base types, because
// there seems little use for it frankly.
if (!type_is_user_defined(parent_type))
{
sema_error_at(context, method_find_overload_span(method),
"Only user-defined types support operator oveloading.");
return false;
}
// See if the operator has already been defined.
OperatorOverload operator = method->operator;
Decl *other = sema_find_operator(context, parent_type, operator);
if (other)
if (other != method)
{
SourceSpan span = method_find_overload_span(method);
sema_error_at(context, span, "This operator is already defined for '%s'.", parent_type->name);
@@ -1920,6 +1921,11 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type
// So compare the value
if (this_value != value)
{
if (operator == OVERLOAD_ELEMENT_REF)
{
value = type_get_ptr(value);
this_value = type_get_ptr(this_value);
}
SEMA_ERROR(method, "There is a mismatch of the 'value' type compared to that of another operator: expected %s but got %s.",
type_quoted_error_string(value), type_quoted_error_string(this_value));
SEMA_NOTE(other, "The other definition is here.");
@@ -1966,6 +1972,7 @@ static inline bool unit_add_method(SemaContext *context, Type *parent_type, Decl
return false;
}
// Is it a base extension?
if (!type_is_user_defined(parent_type)) return unit_add_base_extension_method(context, unit, parent_type, method);
@@ -1984,12 +1991,6 @@ static inline bool unit_add_method(SemaContext *context, Type *parent_type, Decl
return false;
}
// Is it an operator?
if (method->operator)
{
if (!sema_analyse_operator_method(context, parent_type, method)) return false;
}
DEBUG_LOG("Method-like '%s.%s' analysed.", parent->name, method->name);
// Add it to the correct place: type methods, private extensions, local method extensions
@@ -2178,13 +2179,12 @@ static inline bool sema_compare_method_with_interface(SemaContext *context, Decl
*
* 1. Check that it has no init/finalizer attributes.
* 2. Check that it has no test/benchmark attributes.
* 3. Resolve the parent type.
* 4. Resolve the declaration of the parent (as needed).
* 5. Check that it has at least one parameter.
* 6. Check that this parameter is correct.
* 7. If it is dynamic, the type may not be an interface or any
* 8. If it is dynamic, make sure that it implements an interface correctly if available
* 9. Try adding the method
* 3. Resolve the declaration of the parent (as needed).
* 4. Check that it has at least one parameter.
* 5. Check that this parameter is correct.
* 6. If it is dynamic, the type may not be an interface or any
* 7. If it is dynamic, make sure that it implements an interface correctly if available
* 8. Try adding the method
*/
static inline bool sema_analyse_method(SemaContext *context, Decl *decl)
{
@@ -2202,7 +2202,7 @@ static inline bool sema_analyse_method(SemaContext *context, Decl *decl)
// Resolve the parent type.
TypeInfo *parent_type = type_infoptr(decl->func_decl.type_parent);
if (!sema_resolve_type_info(context, parent_type, RESOLVE_TYPE_FUNC_METHOD)) return false;
assert(parent_type->resolve_status == RESOLVE_DONE);
Type *par_type = parent_type->type->canonical;
// Resolve declaration of parent as needed.
@@ -2245,7 +2245,13 @@ static inline bool sema_analyse_method(SemaContext *context, Decl *decl)
decl->func_decl.interface_method = 0;
}
}
return unit_add_method(context, par_type, decl);
// Is it an operator?
if (decl->operator)
{
if (!sema_analyse_operator_method(context, par_type, decl)) return false;
}
return true;
}
static const char *attribute_domain_to_string(AttributeDomain domain)
@@ -2363,6 +2369,7 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_
assert(type >= 0 && type < NUMBER_OF_ATTRIBUTES);
// NOLINTBEGIN(*.EnumCastOutOfRange)
static AttributeDomain attribute_domain[NUMBER_OF_ATTRIBUTES] = {
[ATTRIBUTE_ADHOC] = USER_DEFINED_TYPES,
[ATTRIBUTE_ALIGN] = ATTR_FUNC | ATTR_CONST | ATTR_LOCAL | ATTR_GLOBAL | ATTR_BITSTRUCT | ATTR_STRUCT | ATTR_UNION | ATTR_MEMBER, // NOLINT
[ATTRIBUTE_BENCHMARK] = ATTR_FUNC,
[ATTRIBUTE_BIGENDIAN] = ATTR_BITSTRUCT,
@@ -2526,6 +2533,9 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_
FAILED_OP_TYPE:
RETURN_SEMA_ERROR(attr, "'operator' requires an operator type argument: '[]', '[]=', '&[]' or 'len'.");
}
case ATTRIBUTE_ADHOC:
decl->is_adhoc = true;
return true;
case ATTRIBUTE_ALIGN:
if (!expr)
{
@@ -3462,20 +3472,8 @@ static bool sema_analyse_macro_method(SemaContext *context, Decl *decl)
{
// Resolve the type of the method.
TypeInfo *parent_type_info = type_infoptr(decl->func_decl.type_parent);
if (!sema_resolve_type_info(context, parent_type_info, RESOLVE_TYPE_MACRO_METHOD)) return false;
// Can the type have methods?
Type *parent_type = parent_type_info->type;
if (!type_may_have_method(parent_type))
{
RETURN_SEMA_ERROR(parent_type_info, "Methods can not be associated with '%s'", type_to_error_string(parent_type));
}
// We need at least one argument (the parent type)
if (!vec_size(decl->func_decl.signature.params))
{
RETURN_SEMA_ERROR(decl, "Expected at least one parameter - of type '%s'.", type_to_error_string(parent_type));
}
assert(parent_type_info->resolve_status == RESOLVE_DONE);
Type *parent_type = parent_type_info->type->canonical;
// Check the first argument.
Decl *first_param = decl->func_decl.signature.params[0];
@@ -3484,13 +3482,19 @@ static bool sema_analyse_macro_method(SemaContext *context, Decl *decl)
RETURN_SEMA_ERROR(decl, "The first parameter to this method must be of type '%s'.", type_to_error_string(parent_type));
return false;
}
if (!sema_is_valid_method_param(context, first_param, parent_type->canonical, false)) return false;
if (!sema_is_valid_method_param(context, first_param, parent_type, false)) return false;
if (first_param->var.kind != VARDECL_PARAM_EXPR && first_param->var.kind != VARDECL_PARAM_CT && first_param->var.kind != VARDECL_PARAM_REF && first_param->var.kind != VARDECL_PARAM)
{
RETURN_SEMA_ERROR(first_param, "The first parameter must be a compile time, regular or ref (&) type.");
}
return unit_add_method(context, parent_type->canonical, decl);
// Is it an operator?
if (decl->operator)
{
if (!sema_analyse_operator_method(context, parent_type, decl)) return false;
}
return true;
}
INLINE bool sema_analyse_macro_body(SemaContext *context, Decl **body_parameters)
@@ -4408,6 +4412,31 @@ RETRY:
UNREACHABLE
}
bool sema_analyse_method_register(SemaContext *context, Decl *method)
{
TypeInfo *parent_type_info = type_infoptr(method->func_decl.type_parent);
if (!sema_resolve_type_info(context, parent_type_info, method->decl_kind == DECL_MACRO ? RESOLVE_TYPE_MACRO_METHOD : RESOLVE_TYPE_FUNC_METHOD)) return false;
// Can the type have methods?
Type *parent_type = parent_type_info->type;
if (!type_may_have_method(parent_type))
{
RETURN_SEMA_ERROR(parent_type_info, "Methods can not be associated with '%s'", type_to_error_string(parent_type));
}
// We need at least one argument (the parent type)
if (!vec_size(method->func_decl.signature.params))
{
RETURN_SEMA_ERROR(method, "Expected at least one parameter - of type '%s'.", type_to_error_string(parent_type));
}
// Check the first argument.
Decl *first_param = method->func_decl.signature.params[0];
if (!first_param) RETURN_SEMA_ERROR(method, "The first parameter to this method must be of type '%s'.", type_to_error_string(parent_type));
return unit_add_method(context, parent_type->canonical, method);
}
bool sema_analyse_decl(SemaContext *context, Decl *decl)
{
if (decl->resolve_status == RESOLVE_DONE) return decl_ok(decl);

View File

@@ -2129,7 +2129,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
type_quoted_error_string(type_get_ptr(type_info->type)));
}
body_arg->type = type;
if (type_info && type_storage_type(type_info->type))
if (type_info && type_storage_type(type_info->type) == STORAGE_NORMAL)
{
if (!sema_set_alloca_alignment(context, body_arg->type, &body_arg->alignment)) return false;
}
@@ -3547,6 +3547,7 @@ static inline bool sema_expr_analyse_type_access(SemaContext *context, Expr *exp
UNREACHABLE
}
Decl *member = sema_decl_stack_find_decl_member(context, decl, name);
if (!decl_ok(member)) return false;
if (!member)
@@ -4507,10 +4508,6 @@ static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr,
return true;
}
case TYPE_PROPERTY_METHODSOF:
if (context->compilation_unit->module->stage <= ANALYSIS_DECLS)
{
RETURN_SEMA_ERROR(expr, "'methodsof' is not available until after declaration analysis is complete.");
}
sema_create_const_methodsof(context, expr, flat);
return true;
case TYPE_PROPERTY_PARAMSOF:

View File

@@ -76,6 +76,7 @@ void sema_analysis_pass_register_global_declarations(Module *module);
void sema_analysis_pass_process_includes(Module *module);
void sema_analysis_pass_register_conditional_units(Module *module);
void sema_analysis_pass_register_conditional_declarations(Module *module);
void sema_analysis_pass_process_methods(Module *module);
void sema_analysis_pass_decls(Module *module);
void sema_analysis_pass_ct_assert(Module *module);
void sema_analysis_pass_ct_echo(Module *module);

View File

@@ -367,7 +367,6 @@ void sema_analysis_pass_register_global_declarations(Module *module)
DEBUG_LOG("Processing %s.", unit->file->name);
register_global_decls(unit, unit->global_decls);
}
DEBUG_LOG("Pass finished with %d error(s).", compiler.context.errors_found);
}
@@ -385,6 +384,33 @@ void sema_analysis_pass_process_includes(Module *module)
DEBUG_LOG("Pass finished with %d error(s).", compiler.context.errors_found);
}
void sema_analysis_pass_process_methods(Module *module)
{
DEBUG_LOG("Pass: Process methods register for module '%s'.", module->name->module);
FOREACH(CompilationUnit *, unit, module->units)
{
SemaContext context;
sema_context_init(&context, unit);
FOREACH(Decl *, method, unit->methods_to_register)
{
sema_analyse_method_register(&context, method);
if (method->decl_kind == DECL_MACRO)
{
vec_add(unit->macro_methods, method);
}
else
{
vec_add(unit->methods, method);
}
}
sema_context_destroy(&context);
vec_resize(unit->methods_to_register, 0);
}
DEBUG_LOG("Pass finished with %d error(s).", compiler.context.errors_found);
}
void sema_analysis_pass_register_conditional_units(Module *module)
{
DEBUG_LOG("Pass: Register conditional units for %s", module->name->module);

View File

@@ -192,7 +192,7 @@ static inline bool sema_resolve_array_type(SemaContext *context, TypeInfo *type,
}
static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_info)
static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_info, ResolveTypeKind resolve_type_kind)
{
if (type_info->unresolved.name == type_string->name && !type_info->unresolved.path)
{
@@ -224,8 +224,15 @@ static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_in
type_info->type = decl->type;
type_info->resolve_status = RESOLVE_DONE;
return true;
case DECL_TYPEDEF:
case DECL_DISTINCT:
if (resolve_type_kind & RESOLVE_TYPE_NO_CHECK_DISTINCT)
{
type_info->type = decl->type;
type_info->resolve_status = RESOLVE_DONE;
return true;
}
FALLTHROUGH;
case DECL_TYPEDEF:
if (!sema_analyse_decl(context, decl)) return type_info_poison(type_info);
type_info->type = decl->type;
type_info->resolve_status = RESOLVE_DONE;
@@ -361,10 +368,11 @@ INLINE bool sema_resolve_generic_type(SemaContext *context, TypeInfo *type_info)
Decl *type = sema_analyse_parameterized_identifier(context, inner->unresolved.path, inner->unresolved.name, inner->span, type_info->generic.params);
if (!decl_ok(type)) return false;
type_info->type = type->type;
if (!context->current_macro && (context->call_env.kind == CALL_ENV_FUNCTION || context->call_env.kind == CALL_ENV_FUNCTION_STATIC)
if (!type->is_adhoc && !context->current_macro && (context->call_env.kind == CALL_ENV_FUNCTION || context->call_env.kind == CALL_ENV_FUNCTION_STATIC)
&& !context->call_env.current_function->func_decl.in_macro)
{
SEMA_DEPRECATED(type_info, "Direct generic type declarations outside of macros and type declarations is a deprecated feature, please use 'def' to create an alias.");
SEMA_DEPRECATED(type_info, "Direct generic type declarations not marked '@adhoc' outside of macros and type declarations is a deprecated feature, please use 'def' to create an alias.");
// TODO, completely disallow
// RETURN_SEMA_ERROR(type_info, "Direct generic type declarations are only allowed inside of macros. Use `def` to define an alias for the type instead.");
}
@@ -411,7 +419,7 @@ static inline bool sema_resolve_type(SemaContext *context, TypeInfo *type_info,
case TYPE_INFO_CT_IDENTIFIER:
case TYPE_INFO_IDENTIFIER:
// $Type or Foo
if (!sema_resolve_type_identifier(context, type_info)) return type_info_poison(type_info);
if (!sema_resolve_type_identifier(context, type_info, resolve_type_kind)) return type_info_poison(type_info);
goto APPEND_QUALIFIERS;
case TYPE_INFO_EVALTYPE:
if (!sema_resolve_evaltype(context, type_info, resolve_type_kind)) return type_info_poison(type_info);

View File

@@ -158,15 +158,26 @@ void sema_analyze_stage(Module *module, AnalysisStage stage)
case ANALYSIS_REGISTER_GLOBAL_DECLARATIONS:
sema_analysis_pass_register_global_declarations(module);
break;
case ANALYSIS_METHODS_REGISTER:
sema_analysis_pass_process_methods(module);
break;
case ANALYSIS_INCLUDES:
sema_analysis_pass_process_includes(module);
break;
case ANALYSIS_METHODS_INCLUDES:
sema_analysis_pass_process_methods(module);
break;
case ANALYSIS_REGISTER_CONDITIONAL_UNITS:
sema_analysis_pass_register_conditional_units(module);
break;
case ANALYSIS_REGISTER_CONDITIONAL_DECLARATIONS:
sema_analysis_pass_register_conditional_declarations(module);
break;
case ANALYSIS_METHODS_CONDITIONAL:
sema_analysis_pass_process_methods(module);
break;
case ANALYSIS_POST_REGISTER:
break;
case ANALYSIS_DECLS:
sema_analysis_pass_decls(module);
break;

View File

@@ -312,6 +312,7 @@ void symtab_init(uint32_t capacity)
kw_at_require = KW_DEF("@require");
kw_at_return = KW_DEF("@return");
kw_at_jump = KW_DEF("@jump");
attribute_list[ATTRIBUTE_ADHOC] = KW_DEF("@adhoc");
attribute_list[ATTRIBUTE_ALIGN] = KW_DEF("@align");
attribute_list[ATTRIBUTE_BENCHMARK] = KW_DEF("@benchmark");
attribute_list[ATTRIBUTE_BIGENDIAN] = KW_DEF("@bigendian");

View File

@@ -1,10 +1,12 @@
struct Foo { int a; }
struct Foo2 { int a; }
struct Bar { int a; }
fn int Foo.x(&self, int x) @operator([]) => 1;
fn int Foo.y(&self, int x) @operator(&[]) => 1; // #error: The return type must be a pointer
fn int** Foo.y2(&self, int x) @operator(&[]) => null; // #error: There is a mismatch of the 'value' type
fn void Foo.z(&self, uint x, int a) @operator([]=) {} // #error: There is a mismatch of the 'index'
fn int Foo.y(&self, int x) @operator(&[]) => 1; // #error: The return type must be a pointer
fn int Foo2.y(&self, int x) @operator([]) => null;
fn int** Foo2.y2(&self, int x) @operator(&[]) => null; // #error: There is a mismatch of the 'value' type
fn void Foo.z(&self, uint x, int a) @operator([]=) {} // #error: There is a mismatch of the 'index'
fn double Bar.x(&self, int x) @operator([]) => 0.1;
fn void Bar.y(&self, int x, float y) @operator([]=) {} // #error: There is a mismatch of the 'value'