/* chkutil.c -- Utility functions for the semantic checker Copyright © 2022-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "internal.h" #include "ast.h" #include "hash.h" #include #include #define INTERR_CHKUTIL(errnum) MAKE_INTERR(errnum, INTERRBASE_CHKUTIL) enum DerefMode { DEREF_NEVER, DEREF_NOT_NONE, DEREF_ALWAYS }; static struct Type *real_tr_internal(struct CSlul *ctx, const struct TypeRef *tr, struct TypeRef *out_tr, enum DerefMode deref, const struct ExprNode *sourceexpr, struct TypeDecl **found_decl); int is_pointer_type(struct CSlul *ctx, const struct TypeRef *tr) { const struct Type *type = real_type_tr(ctx, tr); return type->type == T_REF; } /** Returns 1 if the given expression is an identifier of a local variable */ static int is_local_var_ident(const struct ExprNode *n, unsigned *varnum_out) { unsigned varnum; if (n->exprtype == E_IDENT) { const struct IdentDecl *decl = get_identexpr_decl(n); if (!IS_IDENT_LOCAL(decl)) return 0; varnum = ((const struct VarDef *)decl)->var_id; } else if (n->exprtype == E_THIS) { varnum = THIS_VAR_ID; } else { return 0; } if (varnum_out) { *varnum_out = varnum; } return 1; } /* XXX lvalue is not a quite correct name. rename to: - is_complex_lvalue - is_addr_value - is_indirect_lvalue - needs_indirection - needs_temporary (and move to ir.c?) */ int is_lvalue(struct CSlul *ctx, const struct ExprNode *exprnode) { return is_lvalue_ex(ctx, exprnode, NULL); } /** * Checks if a sub-expression needs indirection through a pointer. * This happens e.g. when assigning to a struct field. * * \param ctx Compilation context. * \param exprnode Sub-expression to check. * \param varnum_out If the expression is a local variable, * then the variable number is stored here. * \return 1 if pointer indirection is required, 0 if not. */ int is_lvalue_ex(struct CSlul *ctx, const struct ExprNode *exprnode, unsigned *varnum_out) { /* The outer sub-expression that will consume the output/address of this sub-expression. */ const struct ExprNode *outer = exprnode->rpnnext; if (!outer) { assert(!exprnode->is_element_base); assert(!exprnode->is_assigned); return 0; } else if (outer->exprtype == E_UNARYOP) { /* Force indirection if inside a refto expression */ return outer->op == OP_REFTO; } else if (exprnode->is_element_base) { /* The base (the struct/array) of a field/index operation */ return !is_pointer_type(ctx, &exprnode->u.tr); } else if (is_local_var_ident(exprnode, varnum_out)) { /* Local variables can be assigned with MOVE. */ return 0; } else if (exprnode->is_assigned) { /* Sub-expression is a target of an assignment operation */ return 1; } else { /* TODO Global variables need a pointer */ return 0; } } struct TypeRef root_tr(struct Type *type) { struct TypeRef tr; tr.type = type; tr.quals = MAXIMUM_QUALS; tr.prm = NULL; PROTECT_STACK_STRUCT(tr); return tr; } struct TypeRef root_tr_const(const struct Type *type) { struct TypeRef tr; tr.type = (struct Type *)type; tr.quals = MAXIMUM_QUALS; tr.prm = NULL; PROTECT_STACK_STRUCT(tr); return tr; } struct TypeRef nested_tr(struct Type *type, struct TypeRef *source) { struct TypeRef tr; tr.type = type; tr.quals = source->quals; tr.prm = source->prm; PROTECT_STACK_STRUCT(tr); return tr; } struct TypeRef nested_tr_const(const struct Type *type, const struct TypeRef *source) { struct TypeRef tr; tr.type = (struct Type *)type; tr.quals = source->quals; tr.prm = source->prm; PROTECT_STACK_STRUCT(tr); return tr; } struct Type *get_typescope_type(struct CSlul *ctx, const struct Type *type) { struct TypeChkCtx tc; typechk_ctx_init(&tc); for (;;) { if (UNLIKELY(!detect_type_cycles(NULL, type, &tc, NULL))) return NULL; if (!type) return NULL; else if (type->type == T_IDENT) { if (LIKELY(type->u.ident != NULL)) { return (struct Type *)type; } else { assert(ctx->has_errors); return NULL; } } else if (type->type == T_REF) { type = type->u.nested; /* TODO it would be nice to be able to have typescopes in builtin types like int, etc. */ } else return NULL; } } /** Gets the TypeDecl of the typescope for a given type. This contains all .typeidentifiers in typedecl->typeidents. On error, it reports error and returns NULL. */ struct TypeDecl *get_typescope(struct CSlul *ctx, const struct ExprNode *e, const struct TypeRef *target_tr) { struct Type *identtype = get_typescope_type(ctx, target_tr->type); if (LIKELY(identtype != NULL)) { return identtype->u.ident; } else { /* The return type is parsed after the identifier is parsed */ message_set_expr(ctx, 0, CSLUL_LT_MAIN, ctx->current_exprroot, e); /* TODO include type in error message */ message_final(ctx, CSLUL_E_NOTYPESCOPE); return NULL; } } static int are_toplevels_same(const struct TypeDecl *a, const struct TypeDecl *b) { if (a->type.type == T_IMPORTED) { a = a->type.u.ident; } if (b->type.type == T_IMPORTED) { b = b->type.u.ident; } return a == b; } struct IdentDecl *find_typeident(struct CSlul *ctx, const struct TypeDecl *tsdecl, HashCode h, uint32 len, const char *name, const struct ExprRoot *error_exprroot, const struct ExprNode *error_subexpr, enum CSlulErrorCode errcode) { struct IdentDecl *match; const struct Module *matching_module; const struct Module *same_name_diff_type; const struct TreeNode *same_name_typeidents; struct CSlulDependency *dep; HashCode type_h; uint32 type_len; const char *type_name; struct ClosestSincever closest = { NULL,NULL,NULL,NULL }; /* for errors */ assert(error_exprroot != NULL); assert(error_subexpr != NULL); /* Search in the current module */ match = (struct IdentDecl *)tree_search(ctx, tsdecl->typeidents, h, len, name); /* TODO check version here unless in impl phase */ if (match && IS_IDENT_DEFINED(match)) return match; /* Search in dependencies */ type_h = tsdecl->ident.hashcode; type_len = tsdecl->ident.length; type_name = node_nameptr(&tsdecl->ident); matching_module = NULL; same_name_diff_type = NULL; same_name_typeidents = NULL; for (dep = ctx->module.first_dep; dep; dep = dep->next) { struct VersionedDecl *res; struct TopLevelType *tltype; const struct Module *depmod; if ((dep->flags&CSLUL_DF_NESTEDONLY)!=0 || (!ctx->verifying_impl && (dep->iface_flags&CSLUL_DF_NESTEDONLY)!=0)) { continue; } /* Check if the type exists in the module */ depmod = dep->module; if (UNLIKELY(!depmod)) { assert(ctx->has_errors); continue; } tltype = (struct TopLevelType *)tree_search(ctx, depmod->iface.types_root, type_h, type_len, type_name); if (!tltype) continue; /* Sanity check that the type is available. If not, we *should* either already have an error message, OR the second check_decl_sinceversion should fail, but better safe than sorry. */ if (!check_decl_sinceversion(dep, depmod, tltype->sinceversions, &closest)) continue; if (!are_toplevels_same(tsdecl, &tltype->decl)) { /* Different type which happens to have the same name */ same_name_diff_type = depmod; same_name_typeidents = tltype->decl.typeidents; continue; } /* Check if the typeidentifier exists in the type */ res = (struct VersionedDecl *)tree_search(ctx, tltype->decl.typeidents, h, len, name); if (!res || !IS_IDENT_DEFINED(&res->decl)) continue; if (!check_decl_sinceversion(dep, depmod, res->sinceversions, &closest)) continue; if (UNLIKELY(match)) { /* = exists in multiple modules */ message_set_expr_text(ctx, 0, CSLUL_LT_MAIN, error_exprroot, error_subexpr, name, len); message_set_ident(ctx, 1, CSLUL_LT_DEFINED_IN, &tltype->decl.ident); message_set_module(ctx, 2, CSLUL_LT_DEFINED_IN, depmod); message_set_module(ctx, 3, CSLUL_LT_DEFINED_IN, matching_module); message_final(ctx, CSLUL_E_AMBIGUOUSTYPEIDENT); } match = &res->decl; matching_module = depmod; } if (UNLIKELY(!match && same_name_diff_type && tree_search(ctx, same_name_typeidents, h, len, name))) { message_set_expr_text(ctx, 0, CSLUL_LT_MAIN, error_exprroot, error_subexpr, name, len); message_set_textlen(ctx, 1, CSLUL_LT_DEFINED_IN, node_nameptr(&tsdecl->ident), tsdecl->ident.length); message_set_module(ctx, 2, CSLUL_LT_DEFINED_IN, same_name_diff_type); message_final(ctx, CSLUL_E_SAMECLASSNAMEDIFFTYPE); } if (UNLIKELY(!match)) { enum CSlulErrorCode e; message_set_expr_text(ctx, 0, CSLUL_LT_MAIN, error_exprroot, error_subexpr, name, len); message_set_typedecl(ctx, 1, CSLUL_LT_DEFINITION, tsdecl); if (closest.too_old_dep) { message_set_module(ctx, 2, CSLUL_LT_DEPENDENCY, closest.too_old_module); message_set_textlen(ctx, 3, CSLUL_LT_DEPENDENCY, closest.too_old_dep->min_version, /*requested version*/ closest.too_old_dep->minverlen); if (closest.best_sincever) { e = CSLUL_E_TYPEIDENTTOONEW; } else { e = CSLUL_E_TYPEIDENTLATERLOWER; closest.best_sincever = closest.first_sincever; assert(closest.first_sincever); } message_set_ident(ctx, 4, CSLUL_LT_MINIMUM, &closest.best_sincever->verstr); /*required version*/ } else { /* Not found at all */ e = errcode; } message_final(ctx, e); } return match; } /** * When there is no matching sinceversion for an identifier, * this function can be used to find the nearest match. * Used for error messages. */ static void find_closest_sinceversion( const struct CSlulDependency *dep, const struct Module *module, const struct ApiRefList *sinceversions, struct ClosestSincever *closest) { struct ClosestSincever cl = *closest; const struct ApiRefList *entry; cl.best_sincever = NULL; for (entry = sinceversions; entry; entry = entry->next) { const struct ApiDef *sincever = entry->version; if (!cl.too_old_dep) { cl.too_old_module = module; cl.too_old_dep = dep; } /* Use the minimum version that is higher than the current one in the error message */ if (nodes_vercmp(&sincever->verstr, &dep->effective_apiver->verstr) > 0 && (!cl.best_sincever || nodes_vercmp(&sincever->verstr, &cl.best_sincever->verstr) < 0)) { cl.best_sincever = sincever; cl.too_old_module = module; cl.too_old_dep = dep; } if (!cl.first_sincever || cl.first_sincever->index >= dep->effective_apiver->index) { cl.first_sincever = sincever; } assert(cl.too_old_dep); } *closest = cl; } static int apiver_satisfied(const struct ApiDef *sincever, const struct CSlulDependency *dep) { const struct ApiDef *effver = dep->effective_apiver; if (!effver) { /* Unstable API. No versioning. */ return 1; } /* TODO handle APIDEF_RETRACTED */ return sincever->index <= effver->index /* more recent */ && nodes_vercmp(&sincever->verstr, &effver->verstr) <= 0; /* higher */ } /** * Check that a declaration is actually available in the * effective imported version. Unavailable symbols should * trigger an error if they are used by any identifier that is * available in the effective version. * * \param dep The dependency to search in. * \param module The module dependency. (Note: Nested dependencies * do not have full module information!) * \param sinceversions The sinceversions list of the declaration. * \param closest "Best match". Used in error messages. * \return 1 if available, 0 if not. */ int check_decl_sinceversion( const struct CSlulDependency *dep, const struct Module *module, const struct ApiRefList *sinceversions, struct ClosestSincever *closest) { int ver_match = 0; const struct ApiRefList *entry = sinceversions; if (!entry) return 1; for (; entry; entry = entry->next) { const struct ApiDef *sincever = entry->version; if (apiver_satisfied(sincever, dep)) { ver_match = 1; } } if (!ver_match) { /* Use it in the error message if there is no better match */ find_closest_sinceversion(dep, module, sinceversions, closest); return 0; } return 1; } /** * Finds the dependency where the given toplevel type is declared. * The type must be of type T_IDENT, or something that points to a T_IDENT. * * \param tr Type to search for. * \param sdecl_out Matching definition. * \param dep_out Matching module/dependency * \return Place of the definition: main module, other module or not found. */ enum TypeOrigin find_dep_of_type(struct CSlul *ctx, const struct TypeRef *tr, const struct TypeDecl **sdecl_out, const struct CSlulDependency **dep_out) { const struct CSlulDependency *dep; struct TypeRef out_tr; struct TypeDecl *decl = NULL; real_tr_internal(ctx, tr, &out_tr, DEREF_ALWAYS, NULL, &decl); assert(decl); if (tree_search_node(ctx, ctx->module.iface.types_root, &decl->ident)) { *sdecl_out = decl; return TYORG_MAIN_MODULE; } for (dep = ctx->module.first_dep; dep; dep = dep->next) { struct IdentDecl *res = (struct IdentDecl *)tree_search_node( ctx, dep->module->iface.types_root, &decl->ident); if (res && IS_IDENT_IMPLEMENTED(res)) { *dep_out = dep; *sdecl_out = decl; return TYORG_OTHER_MODULE; } } return TYORG_NOT_FOUND; } struct TypeDecl *get_identtype_decl(const struct Type *type) { struct TypeDecl *td = type->u.ident; if (td->type.type == T_IMPORTED) { td = td->type.u.ident; } return td; } struct IdentDecl *get_identexpr_decl(const struct ExprNode *exprnode) { struct IdentDecl *decl = (struct IdentDecl *)exprnode->a.ident; assert(decl); if (decl->type.type == T_IMPORTED) { decl = (struct IdentDecl *)decl->type.u.ident; } assert(IS_IDENT_DEFINED(decl)); return decl; } static const struct Type *real_type(const struct Type *type) { struct TypeChkCtx tc; typechk_ctx_init(&tc); for (;;) { if (UNLIKELY(!detect_type_cycles(NULL, type, &tc, NULL))) return NULL; if (UNLIKELY(type == NULL || type->type == T_INVALID)) return NULL; if (type->type == T_IDENT || type->type == T_IMPORTED) { if (UNLIKELY(type->u.ident == NULL)) return NULL; type = &type->u.ident->type; /* } else if (type->type == T_GENERICSPEC) { assert(0); type = type->u.gprm->generictype;*/ } else break; } return type; } int bind_type_params(struct CSlul *ctx, const struct Type *type, const struct TypeParam **prm) { const struct TypeParam *param_first; const struct TypeParam **param_lastnextptr; const struct Type *typedef_; const struct PrmDefEntry *prmdef; struct PrmEntry *typearg; assert(type->u.gprm->generictype->type == T_IDENT); typedef_ = real_type(type->u.gprm->generictype); CHK(typedef_); CHK(typedef_->type == T_GENERICDEF); /* TODO just use an index/pointer + a length into some stack structure? XXX problem: lifetimes */ param_first = NULL; param_lastnextptr = ¶m_first; prmdef = &typedef_->u.gdef->paramdef; typearg = &type->u.gprm->param; for (; typearg; typearg = typearg->next) { struct TypeParam *param; CHK(prmdef); /* TODO check prmdef->prmtype (PT_REFTYPE/PT_OWNTYPE/PT_ARENATYPE/PT_ENUMTYPE)? */ param = aallocp(ctx, sizeof(struct TypeParam)); if (!param) return 0; PROTECT_STRUCT(*param); param->next = NULL; /* TODO Seems like List is allowed!?! */ param->t_decl = &prmdef->paramdecl; param->bound_to = &typearg->type; *param_lastnextptr = param; param_lastnextptr = ¶m->next; prmdef = prmdef->next; } CHK(!prmdef); *param_lastnextptr = *prm; /* keep existing params */ *prm = param_first; return 1; assert_error: assert(ctx->has_errors); return 0; } int substitute_type_params(const struct Type **type, const struct TypeParam **prm, unsigned *quals) { const struct Type *t = *type; const struct TypeDecl *t_decl; const struct TypeParam *tp; if (t->type != T_SLOT) return 0; t_decl = t->u.ident; if (t_decl->type.type == T_IMPORTED) { t_decl = t_decl->type.u.ident; } assert(t_decl); for (tp = *prm; tp; tp = tp->next) { if (tp->t_decl == t_decl) { *type = tp->bound_to; /* XXX this will leave stale params, but since new params are added first, it might not necessarilly be problem */ /**prm = NULL;*/ *quals &= t->quals; return 1; } } return 0; } int substitute_type_params_tr(const struct TypeRef **tr, const struct Type **type, struct TypeRef *alloced_tr) { const struct TypeParam *prm = (*tr)->prm; unsigned quals = (*tr)->quals; if (substitute_type_params(type, &prm, &quals)) { alloced_tr->type = *type; alloced_tr->prm = prm; alloced_tr->quals = quals; *tr = alloced_tr; return 1; } return 0; } const struct Type *funcdecl_real_type(const struct Type *type) { const struct Type *ret = type->type == T_GENERICDEF ? &type->u.gdef->basetype : type; assert(!ret || ret->type == T_FUNC || ret->type == T_METHOD); return ret; } static struct Type *real_tr_internal(struct CSlul *ctx, const struct TypeRef *tr, struct TypeRef *out_tr, enum DerefMode deref, const struct ExprNode *sourceexpr, struct TypeDecl **found_decl) { struct TypeChkCtx tc; const struct Type *type = tr->type; const struct TypeParam *prm = tr->prm; unsigned quals = tr->quals; CHECK_STRUCT(*tr); typechk_ctx_init(&tc); for (;;) { CHK(detect_type_cycles(NULL, type, &tc, NULL)); CHK(type != NULL); CHK(type->type != T_INVALID); CHECK_STRUCT(*type); if (type->type == T_REF) { if (deref == DEREF_NEVER) break; if (IS_REF_OPTIONAL(*type) && deref != DEREF_ALWAYS && ctx->varstates) { CHK(sourceexpr); assert(ctx->current_exprroot); if (sourceexpr->exprtype == E_IDENT) { struct IdentDecl *decl = (struct IdentDecl *)sourceexpr->a.ident; assert(decl || ctx->has_errors); assert(sourceexpr != ctx->current_assign_lvalue); assert(sourceexpr != ctx->current_nonecheck_expr); if (decl && IS_IDENT_LOCAL(decl)) { unsigned id = ((struct VarDef *)decl)->var_id; if (!ctx->varstates->vars[id].not_none) { error_expr(ctx, CSLUL_E_VARMAYBENONE, ctx->current_exprroot, sourceexpr); goto not_none; /* don't report another error */ } } goto not_none; } error_expr(ctx, CSLUL_E_CANTACCESSOPTIONAL, ctx->current_exprroot, sourceexpr); } not_none: type = type->u.nested; } else if (type->type == T_IDENT || type->type == T_IMPORTED) { CHK(type->u.ident != NULL); if (found_decl) *found_decl = type->u.ident; type = &type->u.ident->type; } else if (type->type == T_GENERICSPEC) { /* Usage of a generic type */ CHK(bind_type_params(ctx, type, &prm)); assert(prm != NULL); type = type->u.gprm->generictype; /* TODO set prm to a completely new value based on type->u.gprm->params and the existing value? */ } else if (type->type == T_SLOT) { /* Type parameter */ if (UNLIKELY(!substitute_type_params((const struct Type **)&type, &prm, &quals))) { break; } } else if (type->type == T_GENERICDEF) { /* Definition of a generic type */ /* TODO check that there are type params! TODO check that the params match */ /* assert(0);*/ type = &type->u.gdef->basetype; } else break; sourceexpr = NULL; /* No longer valid */ quals &= type->quals; /* Remove inaccessible var/shared/etc. */ } out_tr->type = (struct Type *)type; out_tr->prm = (struct TypeParam *)prm; out_tr->quals = quals; PROTECT_STACK_STRUCT(*out_tr); return (struct Type *)type; assert_error: assert(ctx->has_errors); return NULL; } struct Type *real_tr(struct CSlul *ctx, const struct TypeRef *tr, struct TypeRef *out_tr) { return real_tr_internal(ctx, tr, out_tr, DEREF_NEVER, NULL, NULL); } /** Gets the real type. Dereferences references when they cannot be 'none' (hence it only works from exprchk), or reports an error when they might be 'none' */ struct Type *real_deref_tr(struct CSlul *ctx, const struct TypeRef *tr, const struct ExprNode *sourceexpr, struct TypeRef *out_tr) { return real_tr_internal(ctx, tr, out_tr, DEREF_NOT_NONE, sourceexpr, NULL); } /** Gets the real type. Dereferences all references without checking whether the value might be 'none'. */ struct Type *real_deref_opt_tr(struct CSlul *ctx, const struct TypeRef *tr, struct TypeRef *out_tr) { return real_tr_internal(ctx, tr, out_tr, DEREF_ALWAYS, NULL, NULL); } const struct Type *real_type_tr(struct CSlul *ctx, const struct TypeRef *tr) { struct TypeRef dummy_out; if (UNLIKELY(!tr)) return NULL; return real_tr_internal(ctx, tr, &dummy_out, 0, NULL, NULL); } struct ExprRoot *make_number_expr(struct CSlul *ctx, uint64 num, const struct ExprNode *source) { struct ExprRoot *lengthroot; struct ExprNode *lengthnum = aallocp(ctx, sizeof(struct ExprNode)); if (!lengthnum) return NULL; PROTECT_STRUCT(*lengthnum); lengthnum->exprtype = E_INTEGER; lengthnum->rpntokentype = CSLUL_T_Integer; lengthnum->rpncontext = RC_TERMINAL; lengthnum->rpnnext = NULL; lengthnum->misc = 0; if (source) { lengthnum->line_offset = source->line_offset; lengthnum->column = source->column; } else { lengthnum->line_offset = 0; lengthnum->column = 0; } lengthnum->a.intval = num; lengthroot = aallocp(ctx, sizeof(struct ExprRoot)); if (!lengthroot) return NULL; *lengthroot = *ctx->current_exprroot; PROTECT_STRUCT(*lengthroot); lengthroot->root = lengthroot->rpn = lengthnum; return lengthroot; } void check_seen_typeparams(struct CSlul *ctx, struct TreeNode *actual_params, struct TreeNode *seen_params) { struct TreeIter it; struct TreeNode *seen; for (tree_iter_init(&it, seen_params); tree_iter_next(&it, &seen);) { struct TreeNode *match; struct TypeDecl *decl; match = tree_search_node(ctx, actual_params, seen); decl = (struct TypeDecl *)seen; decl->type.type = T_IMPORTED; decl->type.u.ident = (struct TypeDecl *)match; if (UNLIKELY(!match)) { message_set_ident(ctx, 0, CSLUL_LT_MAIN, seen); message_final(ctx, CSLUL_E_TYPEPARAMNOTFOUND); } } } void typeparams_on_non_generic(struct CSlul *ctx, struct TreeNode *seen_params) { struct TreeIter it; struct TreeNode *seen; for (tree_iter_init(&it, seen_params); tree_iter_next(&it, &seen);) { message_set_ident(ctx, 0, CSLUL_LT_MAIN, seen); message_final(ctx, CSLUL_E_NONPARAMETRICTYPE); } } /** * Checks whether an identifier is "SlulApp.main". * Note that this identifier is only allowed in modules * of application type. */ int ident_is_appmain(const struct TopLevelIdent *tlident) { if (LIKELY(tlident->decl.ident.hashcode != H_MAIN)) return 0; if (tlident->decl.ident.length != 4) return 0; if (!tlident->class_) return 0; return tlident->class_->ident.length == 7 && tlident->class_->ident.hashcode == H_SLULAPP && !memcmp(node_nameptr(&tlident->class_->ident), "SlulApp", 7); } /** * Checks whether a definition of SlulApp.main is valid. * This assumes that the slulrt interface is available and is correct. */ int is_valid_appmain(struct CSlul *ctx, const struct TopLevelIdent *tlident) { struct TypeDecl *rettype; assert(tlident->class_ != NULL); /* ident_is_appmain must be called before */ if (tlident->is_typeident) return 0; if (tlident->decl.type.type != T_METHOD) return 0; if (!HAS_RETURN(&tlident->decl.type)) return 0; if (tlident->decl.type.u.func->params.count != 0) return 0; if (tlident->decl.type.u.func->returntype.type != T_IDENT) return 0; rettype = tlident->decl.type.u.func->returntype.u.ident; if (!rettype) goto assert_error; if (rettype->ident.length != 14 || rettype->ident.hashcode != H_SLULEXITSTATUS || memcmp(node_nameptr(&rettype->ident), "SlulExitStatus", 14)) return 0; return 1; assert_error: assert(ctx->has_errors); return 1; /* don't report more errors */ }