1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[Tolk] Nullable types T? and null safety

This commit introduces nullable types `T?` that are
distinct from non-nullable `T`.
Example: `int?` (int or null) and `int` are different now.
Previously, `null` could be assigned to any primitive type.
Now, it can be assigned only to `T?`.

A non-null assertion operator `!` was also introduced,
similar to `!` in TypeScript and `!!` in Kotlin.

If `int?` still occupies 1 stack slot, `(int,int)?` and
other nullable tensors occupy N+1 slots, the last for
"null precedence". `v == null` actually compares that slot.
Assigning `(int,int)` to `(int,int)?` implicitly creates
a null presence slot. Assigning `null` to `(int,int)?` widens
this null value to 3 slots. This is called "type transitioning".

All stdlib functions prototypes have been updated to reflect
whether they return/accept a nullable or a strict value.

This commit also contains refactoring from `const FunctionData*`
to `FunctionPtr` and similar.
This commit is contained in:
tolk-vm 2025-02-24 20:13:36 +03:00
parent 1389ff6789
commit f3e620f48c
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
62 changed files with 2031 additions and 702 deletions

View file

@ -119,7 +119,7 @@ struct NameAndScopeResolver {
return G.symtable.lookup(name);
}
void add_local_var(const LocalVarData* v_sym) {
void add_local_var(LocalVarPtr v_sym) {
if (UNLIKELY(scopes.empty())) {
throw Fatal("unexpected scope_level = 0");
}
@ -168,9 +168,9 @@ static TypePtr finalize_type_data(TypePtr type_data, const GenericsDeclaration*
class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody {
// more correctly this field shouldn't be static, but currently there is no need to make it a part of state
static NameAndScopeResolver current_scope;
static const FunctionData* current_function;
static FunctionPtr current_function;
static const LocalVarData* create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) {
static LocalVarPtr create_local_var_sym(std::string_view name, SrcLocation loc, TypePtr declared_type, bool immutable) {
LocalVarData* v_sym = new LocalVarData(static_cast<std::string>(name), loc, declared_type, immutable * LocalVarData::flagImmutable, -1);
current_scope.add_local_var(v_sym);
return v_sym;
@ -178,7 +178,7 @@ class AssignSymInsideFunctionVisitor final : public ASTVisitorFunctionBody {
static void process_catch_variable(AnyExprV catch_var) {
if (auto v_ref = catch_var->try_as<ast_reference>()) {
const LocalVarData* var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true);
LocalVarPtr var_ref = create_local_var_sym(v_ref->get_name(), catch_var->loc, nullptr, true);
v_ref->mutate()->assign_sym(var_ref);
}
}
@ -190,14 +190,14 @@ protected:
if (sym == nullptr) {
v->error("`redef` for unknown variable");
}
const LocalVarData* var_ref = sym->try_as<LocalVarData>();
LocalVarPtr var_ref = sym->try_as<LocalVarPtr>();
if (!var_ref) {
v->error("`redef` for unknown variable");
}
v->mutate()->assign_var_ref(var_ref);
} else {
TypePtr declared_type = finalize_type_data(v->declared_type, current_function->genericTs);
const LocalVarData* var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable);
LocalVarPtr var_ref = create_local_var_sym(v->get_name(), v->loc, declared_type, v->is_immutable);
v->mutate()->assign_resolved_type(declared_type);
v->mutate()->assign_var_ref(var_ref);
}
@ -216,7 +216,7 @@ protected:
v->mutate()->assign_sym(sym);
// for global functions, global vars and constants, `import` must exist
if (!sym->try_as<LocalVarData>()) {
if (!sym->try_as<LocalVarPtr>()) {
check_import_exists_when_using_sym(v, sym);
}
@ -276,14 +276,14 @@ protected:
}
public:
bool should_visit_function(const FunctionData* fun_ref) override {
bool should_visit_function(FunctionPtr fun_ref) override {
// this pipe is done just after parsing
// visit both asm and code functions, resolve identifiers in parameter/return types everywhere
// for generic functions, unresolved "T" will be replaced by TypeDataGenericT
return true;
}
void start_visiting_function(const FunctionData* fun_ref, V<ast_function_declaration> v) override {
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v) override {
current_function = fun_ref;
for (int i = 0; i < v->get_num_params(); ++i) {
@ -313,7 +313,7 @@ public:
};
NameAndScopeResolver AssignSymInsideFunctionVisitor::current_scope;
const FunctionData* AssignSymInsideFunctionVisitor::current_function = nullptr;
FunctionPtr AssignSymInsideFunctionVisitor::current_function = nullptr;
void pipeline_resolve_identifiers_and_assign_symbols() {
AssignSymInsideFunctionVisitor visitor;
@ -337,7 +337,7 @@ void pipeline_resolve_identifiers_and_assign_symbols() {
}
}
void pipeline_resolve_identifiers_and_assign_symbols(const FunctionData* fun_ref) {
void pipeline_resolve_identifiers_and_assign_symbols(FunctionPtr fun_ref) {
AssignSymInsideFunctionVisitor visitor;
if (visitor.should_visit_function(fun_ref)) {
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());