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:
parent
1389ff6789
commit
f3e620f48c
62 changed files with 2031 additions and 702 deletions
|
@ -37,12 +37,38 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara
|
|||
if (idx == -1) {
|
||||
throw Fatal("can not replace generic " + asT->nameT);
|
||||
}
|
||||
if (substitutionTs[idx] == nullptr) {
|
||||
throw GenericDeduceError("can not deduce " + asT->nameT);
|
||||
}
|
||||
return substitutionTs[idx];
|
||||
}
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
GenericSubstitutionsDeduceForCall::GenericSubstitutionsDeduceForCall(FunctionPtr fun_ref)
|
||||
: fun_ref(fun_ref) {
|
||||
substitutionTs.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced)
|
||||
}
|
||||
|
||||
void GenericSubstitutionsDeduceForCall::provide_deducedT(const std::string& nameT, TypePtr deduced) {
|
||||
if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) {
|
||||
return; // just 'null' doesn't give sensible info
|
||||
}
|
||||
|
||||
int idx = fun_ref->genericTs->find_nameT(nameT);
|
||||
if (substitutionTs[idx] == nullptr) {
|
||||
substitutionTs[idx] = deduced;
|
||||
} else if (substitutionTs[idx] != deduced) {
|
||||
throw GenericDeduceError(nameT + " is both " + substitutionTs[idx]->as_human_readable() + " and " + deduced->as_human_readable());
|
||||
}
|
||||
}
|
||||
|
||||
void GenericSubstitutionsDeduceForCall::provide_manually_specified(std::vector<TypePtr>&& substitutionTs) {
|
||||
this->substitutionTs = std::move(substitutionTs);
|
||||
this->manually_specified = true;
|
||||
}
|
||||
|
||||
// purpose: having `f<T>(value: T)` and call `f(5)`, deduce T = int
|
||||
// generally, there may be many generic Ts for declaration, and many arguments
|
||||
// for every argument, `consider_next_condition()` is called
|
||||
|
@ -51,71 +77,67 @@ static TypePtr replace_genericT_with_deduced(TypePtr orig, const GenericsDeclara
|
|||
// - next condition: param_type = `T1`, arg_type = `int`, deduce T1 = int
|
||||
// - next condition: param_type = `(T1, T2)`, arg_type = `(int, slice)`, deduce T1 = int, T2 = slice
|
||||
// for call `f(6, cs, (8, cs))` T1 will be both `slice` and `int`, fired an error
|
||||
class GenericSubstitutionsDeduceForFunctionCall final {
|
||||
const FunctionData* fun_ref;
|
||||
std::vector<TypePtr> substitutions;
|
||||
|
||||
void provideDeducedT(const std::string& nameT, TypePtr deduced) {
|
||||
if (deduced == TypeDataNullLiteral::create() || deduced->has_unknown_inside()) {
|
||||
return; // just 'null' doesn't give sensible info
|
||||
void GenericSubstitutionsDeduceForCall::consider_next_condition(TypePtr param_type, TypePtr arg_type) {
|
||||
if (const auto* asT = param_type->try_as<TypeDataGenericT>()) {
|
||||
// `(arg: T)` called as `f([1, 2])` => T is [int, int]
|
||||
provide_deducedT(asT->nameT, arg_type);
|
||||
} else if (const auto* p_nullable = param_type->try_as<TypeDataNullable>()) {
|
||||
// `arg: T?` called as `f(nullableInt)` => T is int
|
||||
if (const auto* a_nullable = arg_type->try_as<TypeDataNullable>()) {
|
||||
consider_next_condition(p_nullable->inner, a_nullable->inner);
|
||||
}
|
||||
|
||||
int idx = fun_ref->genericTs->find_nameT(nameT);
|
||||
if (substitutions[idx] == nullptr) {
|
||||
substitutions[idx] = deduced;
|
||||
} else if (substitutions[idx] != deduced) {
|
||||
throw std::runtime_error(nameT + " is both " + substitutions[idx]->as_human_readable() + " and " + deduced->as_human_readable());
|
||||
// `arg: T?` called as `f(int)` => T is int
|
||||
else {
|
||||
consider_next_condition(p_nullable->inner, arg_type);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit GenericSubstitutionsDeduceForFunctionCall(const FunctionData* fun_ref)
|
||||
: fun_ref(fun_ref) {
|
||||
substitutions.resize(fun_ref->genericTs->size()); // filled with nullptr (nothing deduced)
|
||||
}
|
||||
|
||||
void consider_next_condition(TypePtr param_type, TypePtr arg_type) {
|
||||
if (const auto* asT = param_type->try_as<TypeDataGenericT>()) {
|
||||
// `(arg: T)` called as `f([1, 2])` => T is [int, int]
|
||||
provideDeducedT(asT->nameT, arg_type);
|
||||
} else if (const auto* p_tensor = param_type->try_as<TypeDataTensor>()) {
|
||||
// `arg: (int, T)` called as `f((5, cs))` => T is slice
|
||||
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); a_tensor && a_tensor->size() == p_tensor->size()) {
|
||||
for (int i = 0; i < a_tensor->size(); ++i) {
|
||||
consider_next_condition(p_tensor->items[i], a_tensor->items[i]);
|
||||
}
|
||||
}
|
||||
} else if (const auto* p_tuple = param_type->try_as<TypeDataTypedTuple>()) {
|
||||
// `arg: [int, T]` called as `f([5, cs])` => T is slice
|
||||
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); a_tuple && a_tuple->size() == p_tuple->size()) {
|
||||
for (int i = 0; i < a_tuple->size(); ++i) {
|
||||
consider_next_condition(p_tuple->items[i], a_tuple->items[i]);
|
||||
}
|
||||
}
|
||||
} else if (const auto* p_callable = param_type->try_as<TypeDataFunCallable>()) {
|
||||
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
|
||||
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); a_callable && a_callable->params_size() == p_callable->params_size()) {
|
||||
for (int i = 0; i < a_callable->params_size(); ++i) {
|
||||
consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]);
|
||||
}
|
||||
consider_next_condition(p_callable->return_type, a_callable->return_type);
|
||||
} else if (const auto* p_tensor = param_type->try_as<TypeDataTensor>()) {
|
||||
// `arg: (int, T)` called as `f((5, cs))` => T is slice
|
||||
if (const auto* a_tensor = arg_type->try_as<TypeDataTensor>(); a_tensor && a_tensor->size() == p_tensor->size()) {
|
||||
for (int i = 0; i < a_tensor->size(); ++i) {
|
||||
consider_next_condition(p_tensor->items[i], a_tensor->items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int get_first_not_deduced_idx() const {
|
||||
for (int i = 0; i < static_cast<int>(substitutions.size()); ++i) {
|
||||
if (substitutions[i] == nullptr) {
|
||||
return i;
|
||||
} else if (const auto* p_tuple = param_type->try_as<TypeDataTypedTuple>()) {
|
||||
// `arg: [int, T]` called as `f([5, cs])` => T is slice
|
||||
if (const auto* a_tuple = arg_type->try_as<TypeDataTypedTuple>(); a_tuple && a_tuple->size() == p_tuple->size()) {
|
||||
for (int i = 0; i < a_tuple->size(); ++i) {
|
||||
consider_next_condition(p_tuple->items[i], a_tuple->items[i]);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
} else if (const auto* p_callable = param_type->try_as<TypeDataFunCallable>()) {
|
||||
// `arg: fun(TArg) -> TResult` called as `f(calcTupleLen)` => TArg is tuple, TResult is int
|
||||
if (const auto* a_callable = arg_type->try_as<TypeDataFunCallable>(); a_callable && a_callable->params_size() == p_callable->params_size()) {
|
||||
for (int i = 0; i < a_callable->params_size(); ++i) {
|
||||
consider_next_condition(p_callable->params_types[i], a_callable->params_types[i]);
|
||||
}
|
||||
consider_next_condition(p_callable->return_type, a_callable->return_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TypePtr> flush() {
|
||||
return {std::move(substitutions)};
|
||||
TypePtr GenericSubstitutionsDeduceForCall::replace_by_manually_specified(TypePtr param_type) const {
|
||||
return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs);
|
||||
}
|
||||
|
||||
TypePtr GenericSubstitutionsDeduceForCall::auto_deduce_from_argument(SrcLocation loc, TypePtr param_type, TypePtr arg_type) {
|
||||
try {
|
||||
if (!manually_specified) {
|
||||
consider_next_condition(param_type, arg_type);
|
||||
}
|
||||
return replace_genericT_with_deduced(param_type, fun_ref->genericTs, substitutionTs);
|
||||
} catch (const GenericDeduceError& ex) {
|
||||
throw ParseError(loc, ex.message + " for generic function `" + fun_ref->as_human_readable() + "`; instantiate it manually with `" + fun_ref->name + "<...>()`");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int GenericSubstitutionsDeduceForCall::get_first_not_deduced_idx() const {
|
||||
for (int i = 0; i < static_cast<int>(substitutionTs.size()); ++i) {
|
||||
if (substitutionTs[i] == nullptr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// clone the body of `f<T>` replacing T everywhere with a substitution
|
||||
// before: `fun f<T>(v: T) { var cp: [T] = [v]; }`
|
||||
|
@ -175,7 +197,7 @@ int GenericsDeclaration::find_nameT(std::string_view nameT) const {
|
|||
|
||||
// after creating a deep copy of `f<T>` like `f<int>`, its new and fresh body needs the previous pipeline to run
|
||||
// for example, all local vars need to be registered as symbols, etc.
|
||||
static void run_pipeline_for_instantiated_function(const FunctionData* inst_fun_ref) {
|
||||
static void run_pipeline_for_instantiated_function(FunctionPtr inst_fun_ref) {
|
||||
// these pipes are exactly the same as in tolk.cpp — all preceding (and including) type inferring
|
||||
pipeline_resolve_identifiers_and_assign_symbols(inst_fun_ref);
|
||||
pipeline_calculate_rvalue_lvalue(inst_fun_ref);
|
||||
|
@ -198,34 +220,12 @@ std::string generate_instantiated_name(const std::string& orig_name, const std::
|
|||
return name;
|
||||
}
|
||||
|
||||
td::Result<std::vector<TypePtr>> deduce_substitutionTs_on_generic_func_call(const FunctionData* called_fun, std::vector<TypePtr>&& arg_types, TypePtr return_hint) {
|
||||
try {
|
||||
GenericSubstitutionsDeduceForFunctionCall deducing(called_fun);
|
||||
for (const LocalVarData& param : called_fun->parameters) {
|
||||
if (param.declared_type->has_genericT_inside() && param.param_idx < static_cast<int>(arg_types.size())) {
|
||||
deducing.consider_next_condition(param.declared_type, arg_types[param.param_idx]);
|
||||
}
|
||||
}
|
||||
int idx = deducing.get_first_not_deduced_idx();
|
||||
if (idx != -1 && return_hint && called_fun->declared_return_type->has_genericT_inside()) {
|
||||
deducing.consider_next_condition(called_fun->declared_return_type, return_hint);
|
||||
idx = deducing.get_first_not_deduced_idx();
|
||||
}
|
||||
if (idx != -1) {
|
||||
return td::Status::Error(td::Slice{"can not deduce " + called_fun->genericTs->get_nameT(idx)});
|
||||
}
|
||||
return deducing.flush();
|
||||
} catch (const std::runtime_error& ex) {
|
||||
return td::Status::Error(td::Slice{ex.what()});
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionData* instantiate_generic_function(SrcLocation loc, const FunctionData* fun_ref, const std::string& inst_name, std::vector<TypePtr>&& substitutionTs) {
|
||||
FunctionPtr instantiate_generic_function(SrcLocation loc, FunctionPtr fun_ref, const std::string& inst_name, std::vector<TypePtr>&& substitutionTs) {
|
||||
tolk_assert(fun_ref->genericTs);
|
||||
|
||||
// if `f<int>` was earlier instantiated, return it
|
||||
if (const auto* existing = lookup_global_symbol(inst_name)) {
|
||||
const FunctionData* inst_ref = existing->try_as<FunctionData>();
|
||||
FunctionPtr inst_ref = existing->try_as<FunctionPtr>();
|
||||
tolk_assert(inst_ref);
|
||||
return inst_ref;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue