Merge pull request #70464 from vonagam/unify-assignables
Unify typing of variables, constants and parameters in GDScript
This commit is contained in:
commit
95ce236b7d
17 changed files with 237 additions and 357 deletions
|
@ -758,80 +758,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
switch (member.type) {
|
||||
case GDScriptParser::ClassNode::Member::VARIABLE: {
|
||||
check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable);
|
||||
|
||||
member.variable->set_datatype(resolving_datatype);
|
||||
|
||||
GDScriptParser::DataType datatype;
|
||||
datatype.kind = GDScriptParser::DataType::VARIANT;
|
||||
datatype.type_source = GDScriptParser::DataType::UNDETECTED;
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
if (member.variable->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(member.variable->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
if (member.variable->initializer != nullptr) {
|
||||
reduce_expression(member.variable->initializer);
|
||||
if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
// Typed array.
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer);
|
||||
// Can only infer typed array if it has elements.
|
||||
if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
}
|
||||
datatype = member.variable->initializer->get_datatype();
|
||||
|
||||
if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) {
|
||||
datatype.type_source = GDScriptParser::DataType::INFERRED;
|
||||
}
|
||||
|
||||
if (!datatype.is_set()) {
|
||||
push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.variable->identifier->name), member.variable->initializer);
|
||||
datatype.kind = GDScriptParser::DataType::VARIANT;
|
||||
}
|
||||
}
|
||||
|
||||
if (member.variable->datatype_specifier != nullptr) {
|
||||
datatype = specified_type;
|
||||
|
||||
if (member.variable->initializer != nullptr) {
|
||||
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) {
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
|
||||
} else {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(member.variable->initializer);
|
||||
member.variable->use_conversion_assign = true;
|
||||
}
|
||||
} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
#endif
|
||||
}
|
||||
if (member.variable->initializer->get_datatype().is_variant()) {
|
||||
// TODO: Warn unsafe assign.
|
||||
mark_node_unsafe(member.variable->initializer);
|
||||
member.variable->use_conversion_assign = true;
|
||||
}
|
||||
}
|
||||
} else if (member.variable->infer_datatype) {
|
||||
if (member.variable->initializer == nullptr) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier);
|
||||
} else if (!datatype.is_set() || datatype.has_no_type()) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer);
|
||||
} else if (datatype.is_variant()) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer);
|
||||
} else if (datatype.builtin_type == Variant::NIL) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer);
|
||||
}
|
||||
datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
}
|
||||
|
||||
datatype.is_constant = false;
|
||||
member.variable->set_datatype(datatype);
|
||||
resolve_variable(member.variable, false);
|
||||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
|
||||
|
@ -840,56 +768,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
|
|||
} break;
|
||||
case GDScriptParser::ClassNode::Member::CONSTANT: {
|
||||
check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant);
|
||||
|
||||
member.constant->set_datatype(resolving_datatype);
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
if (member.constant->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(member.constant->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
GDScriptParser::DataType datatype;
|
||||
if (member.constant->initializer) {
|
||||
reduce_expression(member.constant->initializer);
|
||||
datatype = member.constant->initializer->get_datatype();
|
||||
|
||||
if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer);
|
||||
const_fold_array(array);
|
||||
|
||||
// Can only infer typed array if it has elements.
|
||||
if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
} else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) {
|
||||
const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer));
|
||||
}
|
||||
|
||||
if (!datatype.is_set()) {
|
||||
push_error(vformat(R"(Could not resolve initializer for member "%s".)", member.constant->identifier->name), member.constant->initializer);
|
||||
datatype.kind = GDScriptParser::DataType::VARIANT;
|
||||
}
|
||||
|
||||
if (!member.constant->initializer->is_constant) {
|
||||
push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer);
|
||||
}
|
||||
|
||||
if (member.constant->datatype_specifier != nullptr) {
|
||||
datatype = specified_type;
|
||||
|
||||
if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer);
|
||||
} else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
datatype.is_constant = true;
|
||||
|
||||
member.constant->set_datatype(datatype);
|
||||
resolve_constant(member.constant, false);
|
||||
|
||||
// Apply annotations.
|
||||
for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) {
|
||||
|
@ -1310,7 +1190,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
|
|||
}
|
||||
break;
|
||||
case GDScriptParser::Node::CONSTANT:
|
||||
resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node));
|
||||
resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node), true);
|
||||
break;
|
||||
case GDScriptParser::Node::FOR:
|
||||
resolve_for(static_cast<GDScriptParser::ForNode *>(p_node));
|
||||
|
@ -1326,7 +1206,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
|
|||
resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node));
|
||||
break;
|
||||
case GDScriptParser::Node::VARIABLE:
|
||||
resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node));
|
||||
resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node), true);
|
||||
break;
|
||||
case GDScriptParser::Node::WHILE:
|
||||
resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node));
|
||||
|
@ -1426,11 +1306,11 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
|
|||
is_shadowing(p_function->parameters[i]->identifier, "function parameter");
|
||||
#endif // DEBUG_ENABLED
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_function->parameters[i]->default_value) {
|
||||
if (p_function->parameters[i]->initializer) {
|
||||
default_value_count++;
|
||||
|
||||
if (p_function->parameters[i]->default_value->is_constant) {
|
||||
p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value);
|
||||
if (p_function->parameters[i]->initializer->is_constant) {
|
||||
p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value);
|
||||
} else {
|
||||
p_function->default_arg_values.push_back(Variant()); // Prevent shift.
|
||||
}
|
||||
|
@ -1601,6 +1481,132 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
|
|||
}
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind) {
|
||||
GDScriptParser::DataType type;
|
||||
type.kind = GDScriptParser::DataType::VARIANT;
|
||||
|
||||
bool is_variable = p_assignable->type == GDScriptParser::Node::VARIABLE;
|
||||
bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT;
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
bool has_specified_type = p_assignable->datatype_specifier != nullptr;
|
||||
if (has_specified_type) {
|
||||
specified_type = resolve_datatype(p_assignable->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
type = specified_type;
|
||||
}
|
||||
|
||||
if (p_assignable->initializer != nullptr) {
|
||||
reduce_expression(p_assignable->initializer);
|
||||
|
||||
if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer);
|
||||
if ((p_assignable->infer_datatype && array->elements.size() > 0) || (has_specified_type && specified_type.has_container_element_type())) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_constant) {
|
||||
if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer));
|
||||
} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
|
||||
const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer));
|
||||
}
|
||||
if (!p_assignable->initializer->is_constant) {
|
||||
push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer);
|
||||
}
|
||||
}
|
||||
|
||||
GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype();
|
||||
|
||||
if (p_assignable->infer_datatype) {
|
||||
if (!initializer_type.is_set() || initializer_type.has_no_type()) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
|
||||
} else if (initializer_type.is_variant() && !initializer_type.is_hard_type()) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is Variant. Use explicit "Variant" type if this is intended.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
|
||||
} else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) {
|
||||
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
|
||||
}
|
||||
} else {
|
||||
if (!initializer_type.is_set()) {
|
||||
push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_specified_type) {
|
||||
type = initializer_type;
|
||||
|
||||
if (!type.is_set() || (type.is_hard_type() && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL && !is_constant)) {
|
||||
type.kind = GDScriptParser::DataType::VARIANT;
|
||||
}
|
||||
|
||||
if (p_assignable->infer_datatype || is_constant) {
|
||||
type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
} else {
|
||||
type.type_source = GDScriptParser::DataType::INFERRED;
|
||||
}
|
||||
} else if (!specified_type.is_variant()) {
|
||||
if (initializer_type.is_variant() || !initializer_type.is_hard_type()) {
|
||||
mark_node_unsafe(p_assignable->initializer);
|
||||
if (is_variable) {
|
||||
static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true;
|
||||
}
|
||||
} else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) {
|
||||
if (is_variable && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) {
|
||||
mark_node_unsafe(p_assignable->initializer);
|
||||
static_cast<GDScriptParser::VariableNode *>(p_assignable)->use_conversion_assign = true;
|
||||
} else {
|
||||
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
|
||||
parser->push_warning(p_assignable->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type.is_constant = is_constant;
|
||||
p_assignable->set_datatype(type);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local) {
|
||||
static constexpr const char *kind = "variable";
|
||||
resolve_assignable(p_variable, kind);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_is_local) {
|
||||
if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) {
|
||||
parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name);
|
||||
} else if (p_variable->assignments == 0) {
|
||||
parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name);
|
||||
}
|
||||
|
||||
is_shadowing(p_variable->identifier, kind);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local) {
|
||||
static constexpr const char *kind = "constant";
|
||||
resolve_assignable(p_constant, kind);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_is_local) {
|
||||
if (p_constant->usages == 0) {
|
||||
parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name);
|
||||
}
|
||||
|
||||
is_shadowing(p_constant->identifier, kind);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) {
|
||||
static constexpr const char *kind = "parameter";
|
||||
resolve_assignable(p_parameter, kind);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) {
|
||||
reduce_expression(p_if->condition);
|
||||
|
||||
|
@ -1728,148 +1734,6 @@ void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) {
|
|||
p_while->set_datatype(p_while->loop->get_datatype());
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) {
|
||||
GDScriptParser::DataType type;
|
||||
type.kind = GDScriptParser::DataType::VARIANT; // By default.
|
||||
|
||||
GDScriptParser::DataType specified_type;
|
||||
if (p_variable->datatype_specifier != nullptr) {
|
||||
specified_type = resolve_datatype(p_variable->datatype_specifier);
|
||||
specified_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
if (p_variable->initializer != nullptr) {
|
||||
reduce_expression(p_variable->initializer);
|
||||
if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
// Typed array.
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer);
|
||||
// Can only infer typed array if it has elements.
|
||||
if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) {
|
||||
update_array_literal_element_type(specified_type, array);
|
||||
}
|
||||
}
|
||||
|
||||
type = p_variable->initializer->get_datatype();
|
||||
|
||||
if (p_variable->infer_datatype) {
|
||||
type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
|
||||
if (type.has_no_type()) {
|
||||
push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer);
|
||||
} else if (type.is_variant()) {
|
||||
push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer);
|
||||
} else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
|
||||
push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer);
|
||||
}
|
||||
} else {
|
||||
type.type_source = GDScriptParser::DataType::INFERRED;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_variable->datatype_specifier != nullptr) {
|
||||
type = specified_type;
|
||||
type.is_meta_type = false;
|
||||
|
||||
if (p_variable->initializer != nullptr) {
|
||||
if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) {
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
|
||||
} else {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(p_variable->initializer);
|
||||
p_variable->use_conversion_assign = true;
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
|
||||
parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
#endif
|
||||
}
|
||||
if (p_variable->initializer->get_datatype().is_variant() && !type.is_variant()) {
|
||||
// TODO: Warn unsafe assign.
|
||||
mark_node_unsafe(p_variable->initializer);
|
||||
p_variable->use_conversion_assign = true;
|
||||
}
|
||||
}
|
||||
} else if (p_variable->infer_datatype) {
|
||||
if (type.has_no_type()) {
|
||||
push_error(vformat(R"(Cannot infer the type of variable "%s" because the initial value doesn't have a set type.)", p_variable->identifier->name), p_variable->identifier);
|
||||
}
|
||||
type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
}
|
||||
|
||||
type.is_constant = false;
|
||||
p_variable->set_datatype(type);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) {
|
||||
parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name);
|
||||
} else if (p_variable->assignments == 0) {
|
||||
parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name);
|
||||
}
|
||||
|
||||
is_shadowing(p_variable->identifier, "variable");
|
||||
#endif
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) {
|
||||
GDScriptParser::DataType type;
|
||||
|
||||
GDScriptParser::DataType explicit_type;
|
||||
if (p_constant->datatype_specifier != nullptr) {
|
||||
explicit_type = resolve_datatype(p_constant->datatype_specifier);
|
||||
explicit_type.is_meta_type = false;
|
||||
}
|
||||
|
||||
if (p_constant->initializer != nullptr) {
|
||||
reduce_expression(p_constant->initializer);
|
||||
if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) {
|
||||
GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer);
|
||||
const_fold_array(array);
|
||||
|
||||
// Can only infer typed array if it has elements.
|
||||
if (array->elements.size() > 0 || (p_constant->datatype_specifier != nullptr && explicit_type.has_container_element_type())) {
|
||||
update_array_literal_element_type(explicit_type, array);
|
||||
}
|
||||
} else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) {
|
||||
const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer));
|
||||
}
|
||||
|
||||
if (!p_constant->initializer->is_constant) {
|
||||
push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer);
|
||||
}
|
||||
|
||||
type = p_constant->initializer->get_datatype();
|
||||
}
|
||||
|
||||
if (p_constant->datatype_specifier != nullptr) {
|
||||
if (!is_type_compatible(explicit_type, type, true)) {
|
||||
push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer);
|
||||
#ifdef DEBUG_ENABLED
|
||||
} else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) {
|
||||
parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
#endif
|
||||
}
|
||||
type = explicit_type;
|
||||
} else if (p_constant->infer_datatype) {
|
||||
if (type.has_no_type()) {
|
||||
push_error(vformat(R"(Cannot infer the type of constant "%s" because the initial value doesn't have a set type.)", p_constant->identifier->name), p_constant->identifier);
|
||||
}
|
||||
type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
}
|
||||
|
||||
type.is_constant = true;
|
||||
p_constant->set_datatype(type);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_constant->usages == 0) {
|
||||
parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name);
|
||||
}
|
||||
|
||||
is_shadowing(p_constant->identifier, "constant");
|
||||
#endif
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
|
||||
reduce_expression(p_assert->condition);
|
||||
if (p_assert->message != nullptr) {
|
||||
|
@ -1981,41 +1845,6 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc
|
|||
p_match_pattern->set_datatype(result);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) {
|
||||
GDScriptParser::DataType result;
|
||||
result.kind = GDScriptParser::DataType::VARIANT;
|
||||
|
||||
if (p_parameter->default_value != nullptr) {
|
||||
reduce_expression(p_parameter->default_value);
|
||||
result = p_parameter->default_value->get_datatype();
|
||||
if (p_parameter->infer_datatype) {
|
||||
result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED;
|
||||
} else {
|
||||
result.type_source = GDScriptParser::DataType::INFERRED;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_parameter->datatype_specifier != nullptr) {
|
||||
result = resolve_datatype(p_parameter->datatype_specifier);
|
||||
result.is_meta_type = false;
|
||||
|
||||
if (p_parameter->default_value != nullptr) {
|
||||
if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) {
|
||||
push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value);
|
||||
} else if (p_parameter->default_value->get_datatype().is_variant()) {
|
||||
mark_node_unsafe(p_parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.builtin_type == Variant::Type::NIL && result.type_source == GDScriptParser::DataType::ANNOTATED_INFERRED && p_parameter->datatype_specifier == nullptr) {
|
||||
push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value);
|
||||
}
|
||||
|
||||
result.is_constant = false;
|
||||
p_parameter->set_datatype(result);
|
||||
}
|
||||
|
||||
void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
|
||||
GDScriptParser::DataType result;
|
||||
|
||||
|
@ -4171,7 +4000,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
|
|||
r_static = p_is_constructor || found_function->is_static;
|
||||
for (int i = 0; i < found_function->parameters.size(); i++) {
|
||||
r_par_types.push_back(found_function->parameters[i]->get_datatype());
|
||||
if (found_function->parameters[i]->default_value != nullptr) {
|
||||
if (found_function->parameters[i]->initializer != nullptr) {
|
||||
r_default_arg_count++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,16 +69,17 @@ class GDScriptAnalyzer {
|
|||
void resolve_function_body(GDScriptParser::FunctionNode *p_function);
|
||||
void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true);
|
||||
void resolve_suite(GDScriptParser::SuiteNode *p_suite);
|
||||
void resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind);
|
||||
void resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local);
|
||||
void resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local);
|
||||
void resolve_parameter(GDScriptParser::ParameterNode *p_parameter);
|
||||
void resolve_if(GDScriptParser::IfNode *p_if);
|
||||
void resolve_for(GDScriptParser::ForNode *p_for);
|
||||
void resolve_while(GDScriptParser::WhileNode *p_while);
|
||||
void resolve_variable(GDScriptParser::VariableNode *p_variable);
|
||||
void resolve_constant(GDScriptParser::ConstantNode *p_constant);
|
||||
void resolve_assert(GDScriptParser::AssertNode *p_assert);
|
||||
void resolve_match(GDScriptParser::MatchNode *p_match);
|
||||
void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test);
|
||||
void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test);
|
||||
void resolve_parameter(GDScriptParser::ParameterNode *p_parameter);
|
||||
void resolve_return(GDScriptParser::ReturnNode *p_return);
|
||||
|
||||
// Reduction functions.
|
||||
|
|
|
@ -2022,10 +2022,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
for (int i = 0; i < p_func->parameters.size(); i++) {
|
||||
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
|
||||
GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script);
|
||||
uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type);
|
||||
uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type);
|
||||
codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type);
|
||||
|
||||
if (p_func->parameters[i]->default_value != nullptr) {
|
||||
if (p_func->parameters[i]->initializer != nullptr) {
|
||||
optional_parameters++;
|
||||
}
|
||||
}
|
||||
|
@ -2097,7 +2097,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
|||
codegen.generator->start_parameters();
|
||||
for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) {
|
||||
const GDScriptParser::ParameterNode *parameter = p_func->parameters[i];
|
||||
GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value);
|
||||
GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->initializer);
|
||||
if (r_error) {
|
||||
memdelete(codegen.generator);
|
||||
return nullptr;
|
||||
|
|
|
@ -683,37 +683,37 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
|
|||
arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string();
|
||||
}
|
||||
|
||||
if (par->default_value) {
|
||||
if (par->initializer) {
|
||||
String def_val = "<unknown>";
|
||||
switch (par->default_value->type) {
|
||||
switch (par->initializer->type) {
|
||||
case GDScriptParser::Node::LITERAL: {
|
||||
const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value);
|
||||
const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->initializer);
|
||||
def_val = literal->value.get_construct_string();
|
||||
} break;
|
||||
case GDScriptParser::Node::IDENTIFIER: {
|
||||
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value);
|
||||
const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->initializer);
|
||||
def_val = id->name.operator String();
|
||||
} break;
|
||||
case GDScriptParser::Node::CALL: {
|
||||
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->default_value);
|
||||
const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer);
|
||||
if (call->is_constant && call->reduced) {
|
||||
def_val = call->function_name.operator String() + call->reduced_value.operator String();
|
||||
}
|
||||
} break;
|
||||
case GDScriptParser::Node::ARRAY: {
|
||||
const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->default_value);
|
||||
const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer);
|
||||
if (arr->is_constant && arr->reduced) {
|
||||
def_val = arr->reduced_value.operator String();
|
||||
}
|
||||
} break;
|
||||
case GDScriptParser::Node::DICTIONARY: {
|
||||
const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->default_value);
|
||||
const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer);
|
||||
if (dict->is_constant && dict->reduced) {
|
||||
def_val = dict->reduced_value.operator String();
|
||||
}
|
||||
} break;
|
||||
case GDScriptParser::Node::SUBSCRIPT: {
|
||||
const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value);
|
||||
const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer);
|
||||
if (sub->is_constant) {
|
||||
if (sub->datatype.kind == GDScriptParser::DataType::ENUM) {
|
||||
def_val = sub->get_datatype().to_string();
|
||||
|
@ -1856,9 +1856,9 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
|||
}
|
||||
break;
|
||||
case GDScriptParser::SuiteNode::Local::PARAMETER:
|
||||
if (local.parameter->default_value) {
|
||||
last_assign_line = local.parameter->default_value->end_line;
|
||||
last_assigned_expression = local.parameter->default_value;
|
||||
if (local.parameter->initializer) {
|
||||
last_assign_line = local.parameter->initializer->end_line;
|
||||
last_assigned_expression = local.parameter->initializer;
|
||||
}
|
||||
is_function_parameter = true;
|
||||
break;
|
||||
|
@ -1939,12 +1939,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context,
|
|||
if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) {
|
||||
id_type = parameter->get_datatype();
|
||||
}
|
||||
if (parameter->default_value) {
|
||||
if (parameter->initializer) {
|
||||
GDScriptParser::CompletionContext c = p_context;
|
||||
c.current_function = parent_function;
|
||||
c.current_class = base_type.class_type;
|
||||
c.base = nullptr;
|
||||
if (_guess_expression_type(c, parameter->default_value, r_type)) {
|
||||
if (_guess_expression_type(c, parameter->initializer, r_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1219,7 +1219,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
|
|||
|
||||
if (match(GDScriptTokenizer::Token::EQUAL)) {
|
||||
// Default value.
|
||||
parameter->default_value = parse_expression(false);
|
||||
parameter->initializer = parse_expression(false);
|
||||
}
|
||||
|
||||
complete_extents(parameter);
|
||||
|
@ -1250,7 +1250,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
|
|||
push_error("Expected signal parameter name.");
|
||||
break;
|
||||
}
|
||||
if (parameter->default_value != nullptr) {
|
||||
if (parameter->initializer != nullptr) {
|
||||
push_error(R"(Signal parameters cannot have a default value.)");
|
||||
}
|
||||
if (signal->parameters_indices.has(parameter->identifier->name)) {
|
||||
|
@ -1395,7 +1395,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
|
|||
if (parameter == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (parameter->default_value != nullptr) {
|
||||
if (parameter->initializer != nullptr) {
|
||||
default_used = true;
|
||||
} else {
|
||||
if (default_used) {
|
||||
|
@ -4776,9 +4776,9 @@ void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) {
|
|||
push_text(" : ");
|
||||
print_type(p_parameter->datatype_specifier);
|
||||
}
|
||||
if (p_parameter->default_value != nullptr) {
|
||||
if (p_parameter->initializer != nullptr) {
|
||||
push_text(" = ");
|
||||
print_expression(p_parameter->default_value);
|
||||
print_expression(p_parameter->initializer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ public:
|
|||
struct AnnotationNode;
|
||||
struct ArrayNode;
|
||||
struct AssertNode;
|
||||
struct AssignableNode;
|
||||
struct AssignmentNode;
|
||||
struct AwaitNode;
|
||||
struct BinaryOpNode;
|
||||
|
@ -354,6 +355,19 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct AssignableNode : public Node {
|
||||
IdentifierNode *identifier = nullptr;
|
||||
ExpressionNode *initializer = nullptr;
|
||||
TypeNode *datatype_specifier = nullptr;
|
||||
bool infer_datatype = false;
|
||||
int usages = 0;
|
||||
|
||||
virtual ~AssignableNode() {}
|
||||
|
||||
protected:
|
||||
AssignableNode() {}
|
||||
};
|
||||
|
||||
struct AssignmentNode : public ExpressionNode {
|
||||
// Assignment is not really an expression but it's easier to parse as if it were.
|
||||
enum Operation {
|
||||
|
@ -732,12 +746,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct ConstantNode : public Node {
|
||||
IdentifierNode *identifier = nullptr;
|
||||
ExpressionNode *initializer = nullptr;
|
||||
TypeNode *datatype_specifier = nullptr;
|
||||
bool infer_datatype = false;
|
||||
int usages = 0;
|
||||
struct ConstantNode : public AssignableNode {
|
||||
#ifdef TOOLS_ENABLED
|
||||
String doc_description;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
@ -902,13 +911,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct ParameterNode : public Node {
|
||||
IdentifierNode *identifier = nullptr;
|
||||
ExpressionNode *default_value = nullptr;
|
||||
TypeNode *datatype_specifier = nullptr;
|
||||
bool infer_datatype = false;
|
||||
int usages = 0;
|
||||
|
||||
struct ParameterNode : public AssignableNode {
|
||||
ParameterNode() {
|
||||
type = PARAMETER;
|
||||
}
|
||||
|
@ -1157,18 +1160,13 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct VariableNode : public Node {
|
||||
struct VariableNode : public AssignableNode {
|
||||
enum PropertyStyle {
|
||||
PROP_NONE,
|
||||
PROP_INLINE,
|
||||
PROP_SETGET,
|
||||
};
|
||||
|
||||
IdentifierNode *identifier = nullptr;
|
||||
ExpressionNode *initializer = nullptr;
|
||||
TypeNode *datatype_specifier = nullptr;
|
||||
bool infer_datatype = false;
|
||||
|
||||
PropertyStyle property = PROP_NONE;
|
||||
union {
|
||||
FunctionNode *setter = nullptr;
|
||||
|
@ -1184,7 +1182,6 @@ public:
|
|||
bool onready = false;
|
||||
PropertyInfo export_info;
|
||||
int assignments = 0;
|
||||
int usages = 0;
|
||||
bool use_conversion_assign = false;
|
||||
#ifdef TOOLS_ENABLED
|
||||
String doc_description;
|
||||
|
|
|
@ -350,8 +350,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN
|
|||
if (parameter->get_datatype().is_hard_type()) {
|
||||
parameters += ": " + parameter->get_datatype().to_string();
|
||||
}
|
||||
if (parameter->default_value != nullptr) {
|
||||
parameters += " = " + parameter->default_value->reduced_value.to_json_string();
|
||||
if (parameter->initializer != nullptr) {
|
||||
parameters += " = " + parameter->initializer->reduced_value.to_json_string();
|
||||
}
|
||||
}
|
||||
r_symbol.detail += parameters + ")";
|
||||
|
@ -695,8 +695,8 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio
|
|||
Dictionary arg;
|
||||
arg["name"] = p_func->parameters[i]->identifier->name;
|
||||
arg["type"] = p_func->parameters[i]->get_datatype().to_string();
|
||||
if (p_func->parameters[i]->default_value != nullptr) {
|
||||
arg["default_value"] = p_func->parameters[i]->default_value->reduced_value;
|
||||
if (p_func->parameters[i]->initializer != nullptr) {
|
||||
arg["default_value"] = p_func->parameters[i]->initializer->reduced_value;
|
||||
}
|
||||
parameters.push_back(arg);
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".
|
||||
Cannot assign a value of type MyOtherEnum (enum) to variable "class_var" with specified type MyEnum (enum).
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".
|
||||
Cannot assign a value of type MyOtherEnum (enum) to variable "local_var" with specified type MyEnum (enum).
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Value of type "int" cannot be assigned to a variable of type "String".
|
||||
Cannot assign a value of type int to variable "x" with specified type String.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
func check(arg: float = 3):
|
||||
return typeof(arg) == typeof(3.0)
|
||||
|
||||
func test():
|
||||
if check():
|
||||
print('ok')
|
|
@ -0,0 +1,2 @@
|
|||
GDTEST_OK
|
||||
ok
|
|
@ -0,0 +1,32 @@
|
|||
func check(input: int) -> bool:
|
||||
return input == 1
|
||||
|
||||
var recur = null
|
||||
var prop = null
|
||||
|
||||
func check_arg(arg = null) -> void:
|
||||
if arg != null:
|
||||
print(check(arg))
|
||||
|
||||
func check_recur() -> void:
|
||||
if recur != null:
|
||||
print(check(recur))
|
||||
else:
|
||||
recur = 1
|
||||
check_recur()
|
||||
|
||||
func test() -> void:
|
||||
check_arg(1)
|
||||
|
||||
check_recur()
|
||||
|
||||
if prop == null:
|
||||
set('prop', 1)
|
||||
print(check(prop))
|
||||
set('prop', null)
|
||||
|
||||
var loop = null
|
||||
while loop != 2:
|
||||
if loop != null:
|
||||
print(check(loop))
|
||||
loop = 1 if loop == null else 2
|
|
@ -0,0 +1,5 @@
|
|||
GDTEST_OK
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
|
@ -0,0 +1,5 @@
|
|||
func test():
|
||||
var bar = 1
|
||||
var foo: float = bar
|
||||
print(typeof(foo))
|
||||
print(foo is float)
|
|
@ -0,0 +1,3 @@
|
|||
GDTEST_OK
|
||||
3
|
||||
true
|
|
@ -1,2 +1,2 @@
|
|||
GDTEST_ANALYZER_ERROR
|
||||
Assigned value for constant "arr" has type Array[String] which is not compatible with defined type Array[int].
|
||||
Cannot assign a value of type Array[String] to constant "arr" with specified type Array[int].
|
||||
|
|
Loading…
Reference in a new issue