// checker.hpp

struct Type;
struct Entity;
struct Scope;
struct DeclInfo;
struct AstFile;
struct Checker;
struct CheckerInfo;
struct CheckerContext;

enum AddressingMode : u8;
struct TypeAndValue;

// ExprInfo stores information used for "untyped" expressions
struct ExprInfo {
	AddressingMode mode;
	bool is_lhs; // Debug info
	Type *         type;
	ExactValue     value;
};

gb_internal gb_inline ExprInfo *make_expr_info(AddressingMode mode, Type *type, ExactValue const &value, bool is_lhs) {
	ExprInfo *ei = gb_alloc_item(permanent_allocator(), ExprInfo);
	ei->mode   = mode;
	ei->type   = type;
	ei->value  = value;
	ei->is_lhs = is_lhs;
	return ei;
}




enum ExprKind {
	Expr_Expr,
	Expr_Stmt,
};

// Statements and Declarations
enum StmtFlag {
	Stmt_BreakAllowed       = 1<<0,
	Stmt_ContinueAllowed    = 1<<1,
	Stmt_FallthroughAllowed = 1<<2,

	Stmt_TypeSwitch = 1<<4,

	Stmt_CheckScopeDecls = 1<<5,
};

enum BuiltinProcPkg {
	BuiltinProcPkg_builtin,
	BuiltinProcPkg_intrinsics,
	BuiltinProcPkg_COUNT
};

String builtin_proc_pkg_name[BuiltinProcPkg_COUNT] = {
	str_lit("builtin"),
	str_lit("intrinsics"),
};

struct BuiltinProc {
	String   name;
	isize    arg_count;
	bool     variadic;
	ExprKind kind;
	BuiltinProcPkg pkg;
	bool diverging;
	bool ignore_results; // ignores require results handling
};


#include "checker_builtin_procs.hpp"


// Operand is used as an intermediate value whilst checking
// Operands store an addressing mode, the expression being evaluated,
// its type and node, and other specific information for certain
// addressing modes
// Its zero-value is a valid "invalid operand"
struct Operand {
	AddressingMode mode;
	Type *         type;
	ExactValue     value;
	Ast *      expr;
	BuiltinProcId  builtin_id;
	Entity *       proc_group;
};


struct BlockLabel {
	String   name;
	Ast *label; //  Ast_Label;
};

enum DeferredProcedureKind {
	DeferredProcedure_none,
	DeferredProcedure_in,
	DeferredProcedure_out,
	DeferredProcedure_in_out,

	DeferredProcedure_in_by_ptr,
	DeferredProcedure_out_by_ptr,
	DeferredProcedure_in_out_by_ptr,
};
struct DeferredProcedure {
	DeferredProcedureKind kind;
	Entity *entity;
};


enum InstrumentationFlag : i32 {
	Instrumentation_Enabled  = -1,
	Instrumentation_Default  = 0,
	Instrumentation_Disabled = +1,
};

struct AttributeContext {
	String  link_name;
	String  link_prefix;
	String  link_suffix;
	String  link_section;
	String  linkage;
	isize   init_expr_list_count;
	String  thread_local_model;
	String  deprecated_message;
	String  warning_message;
	DeferredProcedure deferred_procedure;
	bool    is_export             : 1;
	bool    is_static             : 1;
	bool    require_results       : 1;
	bool    require_declaration   : 1;
	bool    has_disabled_proc     : 1;
	bool    disabled_proc         : 1;
	bool    test                  : 1;
	bool    init                  : 1;
	bool    fini                  : 1;
	bool    set_cold              : 1;
	bool    entry_point_only      : 1;
	bool    instrumentation_enter : 1;
	bool    instrumentation_exit  : 1;
	bool    no_sanitize_address   : 1;
	bool    no_sanitize_memory    : 1;
	bool    no_sanitize_thread    : 1;
	bool    rodata                : 1;
	bool    ignore_duplicates     : 1;
	u32 optimization_mode; // ProcedureOptimizationMode
	i64 foreign_import_priority_index;
	String extra_linker_flags;
	InstrumentationFlag no_instrumentation;

	String  objc_class;
	String  objc_name;
	String  objc_selector;
	Type *  objc_type;
	Type *  objc_superclass;
	Type *  objc_ivar;
	Entity *objc_context_provider;
	bool    objc_is_class_method;
	bool    objc_is_implementation;     // This struct or proc provides a class/method implementation, not a binding to an existing type.
	bool    objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute.

	String require_target_feature; // required by the target micro-architecture
	String enable_target_feature;  // will be enabled for the procedure only

	bool   raddbg_type_view;
	String raddbg_type_view_string;
};

gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix, String link_suffix) {
	AttributeContext ac = {};
	ac.link_prefix = link_prefix;
	ac.link_suffix = link_suffix;
	return ac;
}

#define DECL_ATTRIBUTE_PROC(_name) bool _name(CheckerContext *c, Ast *elem, String name, Ast *value, AttributeContext *ac)
typedef DECL_ATTRIBUTE_PROC(DeclAttributeProc);

gb_internal void check_decl_attributes(CheckerContext *c, Array<Ast *> const &attributes, DeclAttributeProc *proc, AttributeContext *ac);

#include "name_canonicalization.hpp"

enum ProcCheckedState : u8 {
	ProcCheckedState_Unchecked,
	ProcCheckedState_InProgress,
	ProcCheckedState_Checked,

	ProcCheckedState_COUNT
};

char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
	"Unchecked",
	"In Progress",
	"Checked",
};

struct VariadicReuseData {
	Type *slice_type; // ..elem_type
	i64 max_count;
};

// DeclInfo is used to store information of certain declarations to allow for "any order" usage
struct DeclInfo {
	DeclInfo *    parent; // NOTE(bill): only used for procedure literals at the moment

	BlockingMutex next_mutex;
	DeclInfo *    next_child;
	DeclInfo *    next_sibling;

	Scope *       scope;

	std::atomic<Entity *> entity;

	Ast *         decl_node;
	Ast *         type_expr;
	Ast *         init_expr;
	Array<Ast *>  attributes;
	Ast *         proc_lit;      // Ast_ProcLit
	Type *        gen_proc_type; // Precalculated

	Entity *     para_poly_original;

	bool                          is_using;
	bool                          foreign_require_results;
	std::atomic<bool>             where_clauses_evaluated;
	std::atomic<ProcCheckedState> proc_checked_state;

	BlockingMutex     proc_checked_mutex;
	isize             defer_used;
	std::atomic<bool> defer_use_checked;

	CommentGroup *comment;
	CommentGroup *docs;

	RwMutex          deps_mutex;
	PtrSet<Entity *> deps;

	RwMutex type_info_deps_mutex;
	TypeSet type_info_deps;

	BlockingMutex type_and_value_mutex;

	Array<BlockLabel> labels;

	i32 scope_index;

	Array<VariadicReuseData> variadic_reuses;
	i64 variadic_reuse_max_bytes;
	i64 variadic_reuse_max_align;

	// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
	std::atomic<struct lbModule *> code_gen_module;
};

// ProcInfo stores the information needed for checking a procedure
struct ProcInfo {
	AstFile * file;
	Token     token;
	DeclInfo *decl;
	Type *    type; // Type_Procedure
	Ast *     body; // Ast_BlockStmt
	u64       tags;
	bool      generated_from_polymorphic;
	Ast *     poly_def_node;
};


enum { DEFAULT_SCOPE_CAPACITY = 32 };


struct ScopeMapSlot {
	u32     hash;
	u32     _pad;
	Entity *value;
};

enum { SCOPE_MAP_INLINE_CAP = 16 };

struct ScopeMap {
	InternedString  inline_keys [SCOPE_MAP_INLINE_CAP];
	ScopeMapSlot    inline_slots[SCOPE_MAP_INLINE_CAP];
	InternedString *keys;
	ScopeMapSlot *  slots;
	u32             count;
	u32             cap;
};

gb_internal gb_inline u32 scope_map_max_load(u32 cap) {
	return cap - (cap>>2); // 75%
}

gb_internal gb_inline void scope_map_init(ScopeMap *m) {
	m->cap = SCOPE_MAP_INLINE_CAP;
	m->slots = m->inline_slots;
	m->keys = m->inline_keys;
}


gb_internal Entity *scope_map_insert_for_rehash(
	InternedString *keys, ScopeMapSlot *slots, u32 mask,
	InternedString key, u32 hash, Entity *value) {
	u32 pos = hash & mask;
	u32 dist = 0;

	for (;;) {
		ScopeMapSlot *s = &slots[pos];

		if (s->hash == 0) {
			keys[pos] = key;
			s->hash   = hash;
			s->value  = value;
			return nullptr;
		}

		u32 existing_dist = (pos - s->hash) & mask;

		if (dist > existing_dist) {
			auto    tmp_key   = keys[pos];
			u32     tmp_hash  = s->hash;
			Entity *tmp_value = s->value;

			keys[pos] = key;
			s->hash   = hash;
			s->value  = value;

			hash  = tmp_hash;
			value = tmp_value;
			key   = tmp_key;
			dist  = existing_dist;
		}

		dist += 1;
		pos = (pos+1) & mask;
	}
}

gb_internal gb_inline void scope_map_allocate_entries(u32 cap, InternedString **keys, ScopeMapSlot **slots) {
	Arena *arena = get_arena(ThreadArena_Permanent);
	isize size = (gb_size_of(InternedString) + gb_size_of(ScopeMapSlot)) * cap;
	u8 *data = cast(u8 *)arena_alloc(arena, size, 8);

	*keys  = cast(InternedString *)data;
	*slots = cast(ScopeMapSlot *)(*keys + cap);

	// *keys  = permanent_alloc_array<String>(cap);
	// *slots = permanent_alloc_array<ScopeMapSlot>(cap);
}


gb_internal void scope_map_grow(ScopeMap *m) {
	u32 new_cap = m->cap << 1;
	u32 new_mask = new_cap - 1;

	InternedString *new_keys;
	ScopeMapSlot *  new_slots;
	scope_map_allocate_entries(new_cap, &new_keys, &new_slots);

	if (m->count > 0) {
		for (u32 i = 0; i < m->cap; i++) {
			if (m->slots[i].hash) {
				scope_map_insert_for_rehash(new_keys, new_slots, new_mask,
				                            m->keys[i], m->slots[i].hash, m->slots[i].value);
			}
		}
	}

	m->slots = new_slots;
	m->keys  = new_keys;
	m->cap   = new_cap;
}

gb_internal void scope_map_reserve(ScopeMap *m, isize capacity) {
	if (m->slots == nullptr) {
		scope_map_init(m);
	}
	u32 new_cap = next_pow2_u32(cast(u32)capacity);
	if (m->cap < new_cap && new_cap > SCOPE_MAP_INLINE_CAP) {
		scope_map_allocate_entries(new_cap, &m->keys, &m->slots);
		m->cap   = new_cap;
	}
}



gb_internal Entity *scope_map_insert(ScopeMap *m, InternedString key, u32 hash, Entity *value) {
	if (m->slots == nullptr) {
		scope_map_init(m);
	}
	if (m->count >= scope_map_max_load(m->cap)) {
		scope_map_grow(m);
	}

	u32 mask = m->cap-1;
	u32 pos = hash & mask;
	u32 dist = 0;

	for (;;) {
		ScopeMapSlot *s = &m->slots[pos];

		if (s->hash == 0) {
			m->keys[pos] = key;
			s->hash  = hash;
			s->value = value;
			m->count += 1;
			return nullptr;
		}

		if (s->hash == hash && m->keys[pos] == key) {
			Entity *old = s->value;
			s->value = value;
			return old;
		}

		u32 existing_dist = (pos - s->hash) & mask;

		if (dist > existing_dist) {
			auto    tmp_key   = m->keys[pos];
			u32     tmp_hash  = s->hash;
			Entity *tmp_value = s->value;

			m->keys[pos] = key;
			s->hash      = hash;
			s->value     = value;

			key   = tmp_key;
			hash  = tmp_hash;
			value = tmp_value;
			dist  = existing_dist;
		}

		dist += 1;
		pos = (pos+1) & mask;
	}
}

gb_internal Entity *scope_map_get(ScopeMap *m, InternedString key, u32 hash) {
	u32 mask = m->cap-1;
	u32 pos = hash & mask;
	u32 dist = 0;
	for (;;) {
		ScopeMapSlot *s = &m->slots[pos];
		u32 curr_hash = s->hash;
		if (curr_hash == 0) {
			return nullptr;
		}

		u32 existing_dist = (pos - curr_hash) & mask;
		if (dist > existing_dist) {
			return nullptr;
		}
		if (curr_hash == hash && m->keys[pos] == key) {
			return s->value;
		}

		dist += 1;
		pos = (pos + 1) & mask;
	}
}

gb_internal void scope_map_clear(ScopeMap *m) {
	gb_memset(m->slots, 0, gb_size_of(*m->slots) * m->cap);
	m->count = 0;
}

struct ScopeMapIterator {
	ScopeMap const *map;
	u32 index;

	ScopeMapIterator &operator++() noexcept {
		for (;;) {
			++index;
			if (map->cap == index) {
				return *this;
			}
			ScopeMapSlot *s = map->slots+index;
			if (s->hash) {
				return *this;
			}
		}
	}

	bool operator==(ScopeMapIterator const &other) const noexcept {
		return this->map == other.map && this->index == other.index;
	}

	operator ScopeMapSlot *() const {
		return map->slots+index;
	}
};


gb_internal ScopeMapIterator end(ScopeMap &m) noexcept {
	return ScopeMapIterator{&m, m.cap};
}
gb_internal ScopeMapIterator const end(ScopeMap const &m) noexcept {
	return ScopeMapIterator{&m, m.cap};
}
gb_internal ScopeMapIterator begin(ScopeMap &m) noexcept {
	if (m.count == 0) {
		return end(m);
	}

	u32 index = 0;
	while (index < m.cap) {
		if (m.slots[index].hash) {
			break;
		}
		index++;
	}
	return ScopeMapIterator{&m, index};
}
gb_internal ScopeMapIterator const begin(ScopeMap const &m) noexcept {
	if (m.count == 0) {
		return end(m);
	}

	u32 index = 0;
	while (index < m.cap) {
		if (m.slots[index].hash) {
			break;
		}
		index++;
	}
	return ScopeMapIterator{&m, index};
}

enum ScopeFlag : i32 {
	ScopeFlag_Pkg     = 1<<1,
	ScopeFlag_Builtin = 1<<2,
	ScopeFlag_Global  = 1<<3,
	ScopeFlag_File    = 1<<4,
	ScopeFlag_Init    = 1<<5,
	ScopeFlag_Proc    = 1<<6,
	ScopeFlag_Type    = 1<<7,

	ScopeFlag_HasBeenImported = 1<<10, // This is only applicable to file scopes

	ScopeFlag_ContextDefined = 1<<16,
};


struct Scope {
	Ast *         node;
	Scope *       parent;
	std::atomic<Scope *> next;
	std::atomic<Scope *> head_child;

	i32 index; // within a procedure

	RwMutex mutex;
	ScopeMap elements;
	PtrSet<Scope *> imported;

	DeclInfo *decl_info;

	i32             flags; // ScopeFlag
	union {
		AstPackage *pkg;
		AstFile *   file;
		Entity *    procedure_entity;
	};
};




struct EntityGraphNode;
typedef PtrSet<EntityGraphNode *> EntityGraphNodeSet;

struct EntityGraphNode {
	Entity *entity; // Procedure, Variable, Constant

	EntityGraphNodeSet pred;
	EntityGraphNodeSet succ;
	isize index; // Index in array/queue
	isize dep_count;
};



struct ImportGraphNode;
typedef PtrSet<ImportGraphNode *> ImportGraphNodeSet;


struct ImportGraphNode {
	AstPackage *       pkg;
	Scope *            scope;
	ImportGraphNodeSet pred;
	ImportGraphNodeSet succ;
	isize              index; // Index in array/queue
	isize              dep_count;
};

enum EntityVisiblityKind {
	EntityVisiblity_Public,
	EntityVisiblity_PrivateToPackage,
	EntityVisiblity_PrivateToFile,
};


struct ForeignContext {
	Ast *                 curr_library;
	ProcCallingConvention default_cc;
	String                link_prefix;
	String                link_suffix;
	EntityVisiblityKind   visibility_kind;
	bool                  require_results;
};

typedef Array<Entity *> CheckerTypePath;
typedef Array<Type *>   CheckerPolyPath;

struct AtomOpMapEntry {
	u32  kind;
	Ast *node;
};


struct CheckerContext;

struct UntypedExprInfo {
	Ast *expr;
	ExprInfo *info;
};

typedef PtrMap<Ast *, ExprInfo *> UntypedExprInfoMap; 

enum ObjcMsgKind : u32 {
	ObjcMsg_normal,
	ObjcMsg_fpret,
	ObjcMsg_fp2ret,
	ObjcMsg_stret,
};
struct ObjcMsgData {
	ObjcMsgKind kind;
	Type *proc_type;
};

struct ObjcMethodData {
	AttributeContext ac;
	Entity *proc_entity;
};

enum LoadFileTier {
	LoadFileTier_Invalid,
	LoadFileTier_Exists,
	LoadFileTier_Contents,
};

struct LoadFileCache {
	LoadFileTier   tier;
	bool           exists;
	String         path;
	gbFileError    file_error;
	String         data;
	StringMap<u64> hashes;
};


struct LoadDirectoryFile {
	String file_name;
	String data;
};

struct LoadDirectoryCache {
	String                 path;
	gbFileError            file_error;
	Array<LoadFileCache *> files;
};


struct GenProcsData {
	Array<Entity *> procs;
	RwMutex         mutex;
};

struct GenTypesData {
	Array<Entity *> types;
	RecursiveMutex  mutex;
};

struct Defineable {
	String        name;
	ExactValue    default_value;
	TokenPos      pos;
	CommentGroup *docs;

	// These strings are only computed from previous fields when defineables are being shown or exported.
	String default_value_str;
	String pos_str;
};

struct RaddbgTypeView {
	Type * type;
	String view;
};

// CheckerInfo stores all the symbol information for a type-checked program
struct CheckerInfo {
	Checker *checker;

	StringMap<AstFile *>    files;    // Key (full path)
	StringMap<AstPackage *> packages; // Key (full path)
	Array<DeclInfo *>       variable_init_order;

	AstPackage *          builtin_package;
	AstPackage *          runtime_package;
	AstPackage *          init_package;
	Scope *               init_scope;
	Entity *              entry_point;

	RwMutex minimum_dependency_type_info_mutex;
	PtrMap</*type info hash*/u64, /*min dep index*/isize> min_dep_type_info_index_map;

	RWSpinLock	    min_dep_type_info_set_mutex;
	TypeSet             min_dep_type_info_set;
	Array<TypeInfoPair> type_info_types_hash_map; // 2 * type_info_types.count


	Array<Entity *> testing_procedures;
	Array<Entity *> init_procedures;
	Array<Entity *> fini_procedures;

	Array<Entity *> definitions;
	Array<Entity *> entities;
	Array<Entity *> required_foreign_imports_through_force;

	BlockingMutex     defineables_mutex;
	Array<Defineable> defineables;


	// Below are accessed within procedures
	RwMutex            global_untyped_mutex;
	UntypedExprInfoMap global_untyped; // NOTE(bill): This needs to be a map and not on the Ast
	                                   // as it needs to be iterated across afterwards
	BlockingMutex builtin_mutex;

	BlockingMutex type_and_value_mutex;

	RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files


	// BlockingMutex type_info_mutex; // NOT recursive
	// Array<TypeInfoPair> type_info_types;
	// PtrMap<Type *, isize> type_info_map;
	// TypeSet type_info_set;

	BlockingMutex foreign_mutex; // NOT recursive
	StringMap<Entity *> foreigns;

	MPSCQueue<Entity *> definition_queue;
	MPSCQueue<Entity *> entity_queue;
	MPSCQueue<Entity *> required_global_variable_queue;
	MPSCQueue<Entity *> required_foreign_imports_through_force_queue;
	MPSCQueue<Entity *> foreign_imports_to_check_fullpaths;
	MPSCQueue<Entity *> foreign_decls_to_check;

	MPSCQueue<RaddbgTypeView> raddbg_type_views_queue;
	Array<RaddbgTypeView> raddbg_type_views;

	MPSCQueue<Ast *> intrinsics_entry_point_usage;

	BlockingMutex objc_objc_msgSend_mutex;
	PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;

	BlockingMutex objc_class_name_mutex;
	StringSet obcj_class_name_set;
	MPSCQueue<Entity *> objc_class_implementations;

	BlockingMutex objc_method_mutex;
	PtrMap<Type *, Array<ObjcMethodData>> objc_method_implementations;


	BlockingMutex load_file_mutex;
	StringMap<LoadFileCache *> load_file_cache;

	MPSCQueue<ProcInfo *> all_procedures_queue;
	Array<ProcInfo *> all_procedures;

	BlockingMutex instrumentation_mutex;
	Entity *instrumentation_enter_entity;
	Entity *instrumentation_exit_entity;


	BlockingMutex                       load_directory_mutex;
	StringMap<LoadDirectoryCache *>     load_directory_cache;
	PtrMap<Ast *, LoadDirectoryCache *> load_directory_map; // Key: Ast_CallExpr *


};

struct CheckerContext {
	// Order matters here
	BlockingMutex  mutex;
	Checker *      checker;
	CheckerInfo *  info;

	AstPackage *   pkg;
	AstFile *      file;
	Scope *        scope;
	DeclInfo *     decl;

	// Order doesn't matter after this
	u32            state_flags;
	bool           in_defer;
	Type *         type_hint;
	Ast *          type_hint_expr;

	String         proc_name;
	DeclInfo *     curr_proc_decl;
	Type *         curr_proc_sig;
	ProcCallingConvention curr_proc_calling_convention;
	bool           in_proc_sig;
	ForeignContext foreign_context;

	CheckerTypePath *type_path;
	isize            type_level;

	UntypedExprInfoMap *untyped;

#define MAX_INLINE_FOR_DEPTH 1024ll
	i64 inline_for_depth;

	u32        stmt_flags;
	bool       in_enum_type;
	bool       in_proc_group;
	bool       collect_delayed_decls;
	bool       allow_polymorphic_types;
	bool       disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types
	bool       no_polymorphic_errors;
	bool       hide_polymorphic_errors;
	bool       in_polymorphic_specialization;
	bool       allow_arrow_right_selector_expr;
	u8         bit_field_bit_size;
	Scope *    polymorphic_scope;

	Ast *assignment_lhs_hint;
};

gb_internal u64 check_vet_flags(CheckerContext *c);
gb_internal u64 check_vet_flags(Ast *node);


struct Checker {
	Parser *    parser;
	CheckerInfo info;

	CheckerContext builtin_ctx;

	MPSCQueue<Entity *> procs_with_deferred_to_check;
	MPSCQueue<Entity *> procs_with_objc_context_provider_to_check;
	Array<ProcInfo *> procs_to_check;

	BlockingMutex nested_proc_lits_mutex;
	Array<DeclInfo *> nested_proc_lits;


	MPSCQueue<UntypedExprInfo> global_untyped_queue;
	MPSCQueue<Type *> soa_types_to_complete;
};



gb_global AstPackage *builtin_pkg    = nullptr;
gb_global AstPackage *intrinsics_pkg = nullptr;
gb_global AstPackage *config_pkg      = nullptr;


// CheckerInfo API
gb_internal TypeAndValue type_and_value_of_expr (Ast *expr);
gb_internal Type *       type_of_expr           (Ast *expr);
gb_internal Entity *     implicit_entity_of_node(Ast *clause);
gb_internal DeclInfo *   decl_info_of_ident     (Ast *ident);
gb_internal DeclInfo *   decl_info_of_entity    (Entity * e);
gb_internal AstFile *    ast_file_of_filename   (CheckerInfo *i, String   filename);
// IMPORTANT: Only to use once checking is done
gb_internal isize        type_info_index        (CheckerInfo *i, Type *type, bool error_on_failure);
gb_internal isize        type_info_index        (CheckerInfo *info, TypeInfoPair pair, bool error_on_failure);

// Will return nullptr if not found
gb_internal Entity *entity_of_node(Ast *expr);


// gb_internal Entity *scope_lookup_current(Scope *s, String const &name, u32 hash=0);
gb_internal Entity *scope_lookup (Scope *s, InternedString interned, u32 hash);
gb_internal void    scope_lookup_parent (Scope *s, InternedString name, Scope **scope_, Entity **entity_, u32 hash);
gb_internal Entity *scope_insert (Scope *s, Entity *entity);


gb_internal void      add_type_and_value      (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue const &value);
gb_internal ExprInfo *check_get_expr_info     (CheckerContext *c, Ast *expr);
gb_internal void      add_untyped             (CheckerContext *c, Ast *expression, AddressingMode mode, Type *basic_type, ExactValue const &value);
gb_internal void      add_entity_use          (CheckerContext *c, Ast *identifier, Entity *entity);
gb_internal void      add_implicit_entity     (CheckerContext *c, Ast *node, Entity *e);
gb_internal void      add_entity_and_decl_info(CheckerContext *c, Ast *identifier, Entity *e, DeclInfo *d, bool is_exported=true);
gb_internal void      add_type_info_type      (CheckerContext *c, Type *t);

gb_internal void check_add_import_decl(CheckerContext *c, Ast *decl);
gb_internal void check_add_foreign_import_decl(CheckerContext *c, Ast *decl);


gb_internal void check_entity_decl(CheckerContext *c, Entity *e, DeclInfo *d, Type *named_type);
gb_internal void check_const_decl(CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type);
gb_internal void check_type_decl(CheckerContext *c, Entity *e, Ast *type_expr, Type *def);

gb_internal bool check_arity_match(CheckerContext *c, AstValueDecl *vd, bool is_global = false);
gb_internal void check_collect_entities(CheckerContext *c, Slice<Ast *> const &nodes);
gb_internal void check_collect_entities_from_when_stmt(CheckerContext *c, AstWhenStmt *ws);
gb_internal void check_delayed_file_import_entity(CheckerContext *c, Ast *decl);

gb_internal CheckerTypePath *new_checker_type_path();
gb_internal void destroy_checker_type_path(CheckerTypePath *tp);

gb_internal void    check_type_path_push(CheckerContext *c, Entity *e);
gb_internal Entity *check_type_path_pop (CheckerContext *c);

gb_internal void init_core_context(Checker *c);
gb_internal void init_mem_allocator(Checker *c);

gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped);


gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type);


gb_internal void init_map_internal_types(Type *type);
