1
0
Fork 0

Wrap up GDScript 2.0 base implementation

This commit is contained in:
George Marques 2020-07-15 22:02:44 -03:00
parent aa09b4f85d
commit a0f54cb95e
Signed by: vnen
GPG Key ID: 046BD46A3201E43D
13 changed files with 590 additions and 333 deletions

View File

@ -1873,6 +1873,8 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"func", "func",
"preload", "preload",
"signal", "signal",
"super",
"trait",
"yield", "yield",
// var // var
"const", "const",
@ -2081,36 +2083,28 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori
*r_error = ERR_FILE_CANT_OPEN; *r_error = ERR_FILE_CANT_OPEN;
} }
GDScript *script = memnew(GDScript); Error err;
Ref<GDScript> script = GDScriptCache::get_full_script(p_path, err);
Ref<GDScript> scriptres(script); // TODO: Reintroduce binary and encrypted scripts.
if (p_path.ends_with(".gde") || p_path.ends_with(".gdc")) { if (script.is_null()) {
script->set_script_path(p_original_path); // script needs this. // Don't fail loading because of parsing error.
script->set_path(p_original_path); script.instance();
Error err = script->load_byte_code(p_path);
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'.");
} else {
Error err = script->load_source_code(p_path);
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
script->set_script_path(p_original_path); // script needs this.
script->set_path(p_original_path);
script->reload();
} }
if (r_error) { if (r_error) {
*r_error = OK; *r_error = OK;
} }
return scriptres; return script;
} }
void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("gd"); p_extensions->push_back("gd");
p_extensions->push_back("gdc"); // TODO: Reintroduce binary and encrypted scripts.
p_extensions->push_back("gde"); // p_extensions->push_back("gdc");
// p_extensions->push_back("gde");
} }
bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
@ -2119,7 +2113,8 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const {
String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower(); String el = p_path.get_extension().to_lower();
if (el == "gd" || el == "gdc" || el == "gde") { // TODO: Reintroduce binary and encrypted scripts.
if (el == "gd" /*|| el == "gdc" || el == "gde"*/) {
return "GDScript"; return "GDScript";
} }
return ""; return "";

View File

@ -144,7 +144,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
p_class->fqcn = parser->script_path; p_class->fqcn = parser->script_path;
} }
} else { } else {
p_class->fqcn = p_class->outer->fqcn + "." + String(p_class->identifier->name); p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name);
} }
GDScriptParser::DataType result; GDScriptParser::DataType result;
@ -156,6 +156,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; class_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
class_type.kind = GDScriptParser::DataType::CLASS; class_type.kind = GDScriptParser::DataType::CLASS;
class_type.class_type = p_class; class_type.class_type = p_class;
class_type.script_path = parser->script_path;
p_class->set_datatype(class_type); p_class->set_datatype(class_type);
if (!p_class->extends_used) { if (!p_class->extends_used) {
@ -184,22 +185,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
base = parser->get_parser()->head->get_datatype(); base = parser->get_parser()->head->get_datatype();
} else { } else {
if (p_class->extends.empty()) {
return ERR_PARSE_ERROR;
}
const StringName &name = p_class->extends[extends_index++]; const StringName &name = p_class->extends[extends_index++];
base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; base.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
if (ScriptServer::is_global_class(name)) { if (ScriptServer::is_global_class(name)) {
String base_path = ScriptServer::get_global_class_path(name); String base_path = ScriptServer::get_global_class_path(name);
Ref<GDScriptParserRef> parser = get_parser_for(base_path); if (base_path == parser->script_path) {
if (parser.is_null()) { base = parser->head->get_datatype();
push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class); } else {
return ERR_PARSE_ERROR; Ref<GDScriptParserRef> parser = get_parser_for(base_path);
} if (parser.is_null()) {
push_error(vformat(R"(Could not resolve super class "%s".)", name), p_class);
return ERR_PARSE_ERROR;
}
Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); Error err = parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED);
if (err != OK) { if (err != OK) {
push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class);
return err; return err;
}
} }
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name);
@ -224,13 +232,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
base.native_type = name; base.native_type = name;
} else { } else {
// Look for other classes in script. // Look for other classes in script.
const GDScriptParser::ClassNode *look_class = p_class; GDScriptParser::ClassNode *look_class = p_class;
bool found = false; bool found = false;
while (look_class != nullptr) { while (look_class != nullptr) {
if (p_class->members_indices.has(name) && p_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { if (look_class->identifier && look_class->identifier->name == name) {
GDScriptParser::ClassNode::Member member = p_class->get_member(name); if (!look_class->get_datatype().is_set()) {
base.kind = GDScriptParser::DataType::CLASS; Error err = resolve_inheritance(look_class, false);
base.class_type = member.m_class; if (err) {
return err;
}
}
base = look_class->get_datatype();
found = true;
break;
}
if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) {
GDScriptParser::ClassNode::Member member = look_class->get_member(name);
if (!member.m_class->get_datatype().is_set()) {
Error err = resolve_inheritance(member.m_class, false);
if (err) {
return err;
}
}
base = member.m_class->get_datatype();
found = true; found = true;
break; break;
} }
@ -331,12 +355,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.kind = GDScriptParser::DataType::NATIVE; result.kind = GDScriptParser::DataType::NATIVE;
result.native_type = first; result.native_type = first;
} else if (ScriptServer::is_global_class(first)) { } else if (ScriptServer::is_global_class(first)) {
Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first)); if (parser->script_path == ScriptServer::get_global_class_path(first)) {
if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { result = parser->head->get_datatype();
push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); } else {
return GDScriptParser::DataType(); Ref<GDScriptParserRef> ref = get_parser_for(ScriptServer::get_global_class_path(first));
if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
return GDScriptParser::DataType();
}
result = ref->get_parser()->head->get_datatype();
} }
result = ref->get_parser()->head->get_datatype();
} else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
Ref<GDScriptParserRef> ref = get_parser_for(autoload.path); Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
@ -353,12 +381,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
GDScriptParser::ClassNode *script_class = parser->current_class; GDScriptParser::ClassNode *script_class = parser->current_class;
bool found = false; bool found = false;
while (!found && script_class != nullptr) { while (!found && script_class != nullptr) {
if (script_class->identifier && script_class->identifier->name == first) {
result = script_class->get_datatype();
found = true;
break;
}
if (script_class->members_indices.has(first)) { if (script_class->members_indices.has(first)) {
GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]];
switch (member.type) { switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS: case GDScriptParser::ClassNode::Member::CLASS:
result.kind = GDScriptParser::DataType::CLASS; result = member.m_class->get_datatype();
result.class_type = member.m_class;
found = true; found = true;
break; break;
case GDScriptParser::ClassNode::Member::ENUM: case GDScriptParser::ClassNode::Member::ENUM:
@ -382,7 +414,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} }
if (!result.is_set()) { if (!result.is_set()) {
push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type); push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type);
return GDScriptParser::DataType(); result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
return result;
} }
if (p_type->type_chain.size() > 1) { if (p_type->type_chain.size() > 1) {
@ -393,10 +426,12 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result = p_type->type_chain[i]->get_datatype(); result = p_type->type_chain[i]->get_datatype();
if (!result.is_set()) { if (!result.is_set()) {
push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]);
return GDScriptParser::DataType(); result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
return result;
} else if (!result.is_meta_type) { } else if (!result.is_meta_type) {
push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]);
return GDScriptParser::DataType(); result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
return result;
} }
} }
} else if (result.kind == GDScriptParser::DataType::NATIVE) { } else if (result.kind == GDScriptParser::DataType::NATIVE) {
@ -410,7 +445,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
} }
} else { } else {
push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]);
return GDScriptParser::DataType(); result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type.
return result;
} }
} }
@ -444,12 +480,15 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
if (member.variable->datatype_specifier != nullptr) { if (member.variable->datatype_specifier != nullptr) {
datatype = resolve_datatype(member.variable->datatype_specifier); datatype = resolve_datatype(member.variable->datatype_specifier);
datatype.is_meta_type = false;
if (member.variable->initializer != nullptr) { if (member.variable->initializer != nullptr) {
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
} else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { } 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); parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
#endif
} }
if (member.variable->initializer->get_datatype().is_variant()) { if (member.variable->initializer->get_datatype().is_variant()) {
// TODO: Warn unsafe assign. // TODO: Warn unsafe assign.
@ -496,29 +535,34 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
case GDScriptParser::ClassNode::Member::CONSTANT: { case GDScriptParser::ClassNode::Member::CONSTANT: {
reduce_expression(member.constant->initializer); reduce_expression(member.constant->initializer);
if (!member.constant->initializer->is_constant) {
push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer);
}
GDScriptParser::DataType datatype = member.constant->get_datatype(); GDScriptParser::DataType datatype = member.constant->get_datatype();
if (member.constant->initializer) {
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) { if (member.constant->datatype_specifier != nullptr) {
datatype = resolve_datatype(member.constant->datatype_specifier); datatype = resolve_datatype(member.constant->datatype_specifier);
datatype.is_meta_type = false;
if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { 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); 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) { } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) {
parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); #ifdef DEBUG_ENABLED
parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
#endif
}
} }
} }
datatype.is_constant = true; datatype.is_constant = true;
member.constant->set_datatype(datatype); member.constant->set_datatype(datatype);
} break; } break;
case GDScriptParser::ClassNode::Member::SIGNAL: { case GDScriptParser::ClassNode::Member::SIGNAL: {
for (int j = 0; j < member.signal->parameters.size(); j++) { for (int j = 0; j < member.signal->parameters.size(); j++) {
member.signal->parameters[j]->set_datatype(resolve_datatype(member.signal->parameters[j]->datatype_specifier)); GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier);
signal_type.is_meta_type = false;
member.signal->parameters[j]->set_datatype(signal_type);
} }
// TODO: Make MethodInfo from signal. // TODO: Make MethodInfo from signal.
GDScriptParser::DataType signal_type; GDScriptParser::DataType signal_type;
@ -538,8 +582,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
enum_type.is_meta_type = true; enum_type.is_meta_type = true;
enum_type.is_constant = true; enum_type.is_constant = true;
for (int i = 0; i < member.m_enum->values.size(); i++) { for (int j = 0; j < member.m_enum->values.size(); j++) {
enum_type.enum_values[member.m_enum->values[i].identifier->name] = member.m_enum->values[i].value; enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value;
} }
member.m_enum->set_datatype(enum_type); member.m_enum->set_datatype(enum_type);
@ -607,9 +651,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) { if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) {
continue; continue;
} }
#ifdef DEBUG_ENABLED
if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) {
parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name);
} }
#endif
} }
} }
@ -713,14 +759,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
for (int i = 0; i < p_function->parameters.size(); i++) { for (int i = 0; i < p_function->parameters.size(); i++) {
resolve_pararameter(p_function->parameters[i]); resolve_pararameter(p_function->parameters[i]);
#ifdef DEBUG_ENABLED
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name);
} }
is_shadowing(p_function->parameters[i]->identifier, "function parameter"); is_shadowing(p_function->parameters[i]->identifier, "function parameter");
#endif
} }
p_function->set_datatype(resolve_datatype(p_function->return_type)); if (p_function->identifier->name == "_init") {
// Constructor.
GDScriptParser::DataType return_type = parser->current_class->get_datatype();
return_type.is_meta_type = false;
p_function->set_datatype(return_type);
if (p_function->return_type) {
push_error("Constructor cannot have an explicit return type.", p_function->return_type);
}
} else {
GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type);
p_function->set_datatype(return_type);
}
parser->current_function = previous_function; parser->current_function = previous_function;
} }
@ -743,7 +801,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
return_type.type_source = GDScriptParser::DataType::INFERRED; return_type.type_source = GDScriptParser::DataType::INFERRED;
p_function->set_datatype(p_function->body->get_datatype()); p_function->set_datatype(p_function->body->get_datatype());
} else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) {
if (!p_function->body->has_return) { if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) {
push_error(R"(Not all code paths return a value.)", p_function); push_error(R"(Not all code paths return a value.)", p_function);
} }
} }
@ -752,6 +810,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
} }
void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) { void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) {
if (p_statement == nullptr) {
return;
}
switch (p_statement->type) { switch (p_statement->type) {
case GDScriptParser::Node::IF: case GDScriptParser::Node::IF:
case GDScriptParser::Node::FOR: case GDScriptParser::Node::FOR:
@ -799,7 +860,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
// Optimize constant range() call to not allocate an array. // Optimize constant range() call to not allocate an array.
// Use int, Vector2, Vector3 instead, which also can be used as range iterators. // Use int, Vector2, Vector3 instead, which also can be used as range iterators.
if (p_for->list->type == GDScriptParser::Node::CALL) { if (p_for->list && p_for->list->type == GDScriptParser::Node::CALL) {
GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list); GDScriptParser::CallNode *call = static_cast<GDScriptParser::CallNode *>(p_for->list);
if (call->callee->type == GDScriptParser::Node::IDENTIFIER) { if (call->callee->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee); GDScriptParser::IdentifierNode *callee = static_cast<GDScriptParser::IdentifierNode *>(call->callee);
@ -870,8 +931,11 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
resolve_suite(p_for->loop); resolve_suite(p_for->loop);
p_for->set_datatype(p_for->loop->get_datatype()); p_for->set_datatype(p_for->loop->get_datatype());
#ifdef DEBUG_ENABLED
is_shadowing(p_for->variable, R"("for" iterator variable)"); if (p_for->variable) {
is_shadowing(p_for->variable, R"("for" iterator variable)");
}
#endif
} }
void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) {
@ -896,25 +960,30 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
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); 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()) { } 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); 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.builtin_type == Variant::NIL) { } 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); 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 { } else {
type.type_source = GDScriptParser::DataType::INFERRED; type.type_source = GDScriptParser::DataType::INFERRED;
} }
#ifdef DEBUG_ENABLED
if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name); parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name);
} }
#endif
} }
if (p_variable->datatype_specifier != nullptr) { if (p_variable->datatype_specifier != nullptr) {
type = resolve_datatype(p_variable->datatype_specifier); type = resolve_datatype(p_variable->datatype_specifier);
type.is_meta_type = false;
if (p_variable->initializer != nullptr) { if (p_variable->initializer != nullptr) {
if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) { if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); push_error(vformat(R"(Value of type "%s" cannot be assigned to variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
#ifdef DEBUG_ENABLED
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { } 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); parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
#endif
} }
if (p_variable->initializer->get_datatype().is_variant()) { if (p_variable->initializer->get_datatype().is_variant()) {
// TODO: Warn unsafe assign. // TODO: Warn unsafe assign.
@ -931,6 +1000,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
type.is_constant = false; type.is_constant = false;
p_variable->set_datatype(type); p_variable->set_datatype(type);
#ifdef DEBUG_ENABLED
if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) {
parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name);
} else if (p_variable->assignments == 0) { } else if (p_variable->assignments == 0) {
@ -938,6 +1008,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
} }
is_shadowing(p_variable->identifier, "variable"); is_shadowing(p_variable->identifier, "variable");
#endif
} }
void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) {
@ -951,16 +1022,21 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant
type = p_constant->initializer->get_datatype(); type = p_constant->initializer->get_datatype();
#ifdef DEBUG_ENABLED
if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) {
parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name); parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name);
} }
#endif
if (p_constant->datatype_specifier != nullptr) { if (p_constant->datatype_specifier != nullptr) {
GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier); GDScriptParser::DataType explicit_type = resolve_datatype(p_constant->datatype_specifier);
explicit_type.is_meta_type = false;
if (!is_type_compatible(explicit_type, type)) { if (!is_type_compatible(explicit_type, type)) {
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); 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) { } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) {
parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION); parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION);
#endif
} }
type = explicit_type; type = explicit_type;
} else if (p_constant->infer_datatype) { } else if (p_constant->infer_datatype) {
@ -973,11 +1049,13 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant
type.is_constant = true; type.is_constant = true;
p_constant->set_datatype(type); p_constant->set_datatype(type);
#ifdef DEBUG_ENABLED
if (p_constant->usages == 0) { if (p_constant->usages == 0) {
parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name);
} }
is_shadowing(p_constant->identifier, "constant"); is_shadowing(p_constant->identifier, "constant");
#endif
} }
void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
@ -988,6 +1066,7 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
p_assert->set_datatype(p_assert->condition->get_datatype()); p_assert->set_datatype(p_assert->condition->get_datatype());
#ifdef DEBUG_ENABLED
if (p_assert->condition->is_constant) { if (p_assert->condition->is_constant) {
if (p_assert->condition->reduced_value.booleanize()) { if (p_assert->condition->reduced_value.booleanize()) {
parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE); parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE);
@ -995,6 +1074,7 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) {
parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE); parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE);
} }
} }
#endif
} }
void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) { void GDScriptAnalyzer::resolve_match(GDScriptParser::MatchNode *p_match) {
@ -1018,19 +1098,27 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m
} }
void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) { void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test) {
if (p_match_pattern == nullptr) {
return;
}
GDScriptParser::DataType result; GDScriptParser::DataType result;
switch (p_match_pattern->pattern_type) { switch (p_match_pattern->pattern_type) {
case GDScriptParser::PatternNode::PT_LITERAL: case GDScriptParser::PatternNode::PT_LITERAL:
reduce_literal(p_match_pattern->literal); if (p_match_pattern->literal) {
result = p_match_pattern->literal->get_datatype(); reduce_literal(p_match_pattern->literal);
result = p_match_pattern->literal->get_datatype();
}
break; break;
case GDScriptParser::PatternNode::PT_EXPRESSION: case GDScriptParser::PatternNode::PT_EXPRESSION:
reduce_expression(p_match_pattern->expression); if (p_match_pattern->expression) {
if (!p_match_pattern->expression->is_constant) { reduce_expression(p_match_pattern->expression);
push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression); if (!p_match_pattern->expression->is_constant) {
push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression);
}
result = p_match_pattern->expression->get_datatype();
} }
result = p_match_pattern->expression->get_datatype();
break; break;
case GDScriptParser::PatternNode::PT_BIND: case GDScriptParser::PatternNode::PT_BIND:
if (p_match_test != nullptr) { if (p_match_test != nullptr) {
@ -1039,10 +1127,12 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc
result.kind = GDScriptParser::DataType::VARIANT; result.kind = GDScriptParser::DataType::VARIANT;
} }
p_match_pattern->bind->set_datatype(result); p_match_pattern->bind->set_datatype(result);
#ifdef DEBUG_ENABLED
is_shadowing(p_match_pattern->bind, "pattern bind"); is_shadowing(p_match_pattern->bind, "pattern bind");
if (p_match_pattern->bind->usages == 0) { if (p_match_pattern->bind->usages == 0) {
parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name); parser->push_warning(p_match_pattern->bind, GDScriptWarning::UNASSIGNED_VARIABLE, p_match_pattern->bind->name);
} }
#endif
break; break;
case GDScriptParser::PatternNode::PT_ARRAY: case GDScriptParser::PatternNode::PT_ARRAY:
for (int i = 0; i < p_match_pattern->array.size(); i++) { for (int i = 0; i < p_match_pattern->array.size(); i++) {
@ -1053,13 +1143,17 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc
break; break;
case GDScriptParser::PatternNode::PT_DICTIONARY: case GDScriptParser::PatternNode::PT_DICTIONARY:
for (int i = 0; i < p_match_pattern->dictionary.size(); i++) { for (int i = 0; i < p_match_pattern->dictionary.size(); i++) {
reduce_expression(p_match_pattern->dictionary[i].key); if (p_match_pattern->dictionary[i].key) {
if (!p_match_pattern->dictionary[i].key->is_constant) { reduce_expression(p_match_pattern->dictionary[i].key);
push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression); if (!p_match_pattern->dictionary[i].key->is_constant) {
push_error(R"(Expression in dictionary pattern key must be a constant.)", p_match_pattern->expression);
}
} }
resolve_match_pattern(p_match_pattern->dictionary[i].value_pattern, nullptr); if (p_match_pattern->dictionary[i].value_pattern) {
decide_suite_type(p_match_pattern, p_match_pattern->dictionary[i].value_pattern); resolve_match_pattern(p_match_pattern->dictionary[i].value_pattern, nullptr);
decide_suite_type(p_match_pattern, p_match_pattern->dictionary[i].value_pattern);
}
} }
result = p_match_pattern->get_datatype(); result = p_match_pattern->get_datatype();
break; break;
@ -1084,14 +1178,16 @@ void GDScriptAnalyzer::resolve_pararameter(GDScriptParser::ParameterNode *p_para
if (p_parameter->datatype_specifier != nullptr) { if (p_parameter->datatype_specifier != nullptr) {
resolve_datatype(p_parameter->datatype_specifier); resolve_datatype(p_parameter->datatype_specifier);
result = p_parameter->datatype_specifier->get_datatype();
result.is_meta_type = false;
if (p_parameter->default_value != nullptr) { if (p_parameter->default_value != nullptr) {
if (!is_type_compatible(p_parameter->datatype_specifier->get_datatype(), p_parameter->default_value->get_datatype())) { 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 paremeter 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); push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with paremeter 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);
} }
} }
result = p_parameter->datatype_specifier->get_datatype();
} }
p_parameter->set_datatype(result); p_parameter->set_datatype(result);
@ -1112,6 +1208,7 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} }
GDScriptParser::DataType function_type = parser->current_function->get_datatype(); GDScriptParser::DataType function_type = parser->current_function->get_datatype();
function_type.is_meta_type = false;
if (function_type.is_hard_type()) { if (function_type.is_hard_type()) {
if (!is_type_compatible(function_type, result)) { if (!is_type_compatible(function_type, result)) {
// Try other way. Okay but not safe. // Try other way. Okay but not safe.
@ -1121,10 +1218,12 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
// TODO: Add warning. // TODO: Add warning.
mark_node_unsafe(p_return); mark_node_unsafe(p_return);
} }
#ifdef DEBUG_ENABLED
} else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) { } else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION); parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
} else if (result.is_variant()) { } else if (result.is_variant()) {
mark_node_unsafe(p_return); mark_node_unsafe(p_return);
#endif
} }
} }
@ -1260,7 +1359,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee); push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
} }
if (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype())) { if (!is_type_compatible(p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), true)) {
if (p_assignment->assignee->get_datatype().is_hard_type()) { if (p_assignment->assignee->get_datatype().is_hard_type()) {
push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value);
} else { } else {
@ -1311,11 +1410,13 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype();
GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype(); GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype();
#ifdef DEBUG_ENABLED
if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) { if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) {
parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name);
} else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) { } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) {
parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION);
} }
#endif
} }
void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) {
@ -1332,27 +1433,41 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) {
p_await->set_datatype(awaiting_type); p_await->set_datatype(awaiting_type);
#ifdef DEBUG_ENABLED
if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) { if (!awaiting_type.is_coroutine && awaiting_type.builtin_type != Variant::SIGNAL) {
parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT); parser->push_warning(p_await, GDScriptWarning::REDUNDANT_AWAIT);
} }
#endif
} }
void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) { void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op) {
reduce_expression(p_binary_op->left_operand); reduce_expression(p_binary_op->left_operand);
if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) { if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST && p_binary_op->right_operand && p_binary_op->right_operand->type == GDScriptParser::Node::IDENTIFIER) {
reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true); reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_binary_op->right_operand), true);
} else { } else {
reduce_expression(p_binary_op->right_operand); reduce_expression(p_binary_op->right_operand);
} }
// TODO: Right operand must be a valid type with the `is` operator. Need to check here. // TODO: Right operand must be a valid type with the `is` operator. Need to check here.
GDScriptParser::DataType left_type = p_binary_op->left_operand->get_datatype(); GDScriptParser::DataType left_type;
GDScriptParser::DataType right_type = p_binary_op->right_operand->get_datatype(); if (p_binary_op->left_operand) {
left_type = p_binary_op->left_operand->get_datatype();
}
GDScriptParser::DataType right_type;
if (p_binary_op->right_operand) {
right_type = p_binary_op->right_operand->get_datatype();
}
if (!left_type.is_set() || !right_type.is_set()) {
return;
}
#ifdef DEBUG_ENABLED
if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) { if (p_binary_op->variant_op == Variant::OP_DIVIDE && left_type.builtin_type == Variant::INT && right_type.builtin_type == Variant::INT) {
parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION); parser->push_warning(p_binary_op, GDScriptWarning::INTEGER_DIVISION);
} }
#endif
if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) { if (p_binary_op->left_operand->is_constant && p_binary_op->right_operand->is_constant) {
p_binary_op->is_constant = true; p_binary_op->is_constant = true;
@ -1398,11 +1513,14 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
test_type.is_meta_type = false; test_type.is_meta_type = false;
if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) {
if (p_binary_op->left_operand->get_datatype().is_hard_type()) { // Test reverse as well to consider for subtypes.
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) {
} else { if (p_binary_op->left_operand->get_datatype().is_hard_type()) {
// TODO: Warning. push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand);
mark_node_unsafe(p_binary_op); } else {
// TODO: Warning.
mark_node_unsafe(p_binary_op);
}
} }
} }
@ -1522,10 +1640,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) { if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) {
types_match = false; types_match = false;
break; break;
#ifdef DEBUG_ENABLED
} else { } else {
if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT) { if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT) {
parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
} }
#endif
} }
} }
@ -1649,18 +1769,22 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
// Can only be attribute. // Can only be attribute.
callee_id = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee)->attribute; callee_id = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee)->attribute;
} }
reduce_identifier_from_base(callee_id, &base_type); if (callee_id) {
GDScriptParser::DataType callee_type = callee_id->get_datatype(); reduce_identifier_from_base(callee_id, &base_type);
if (callee_type.is_set() && !callee_type.is_variant()) { GDScriptParser::DataType callee_type = callee_id->get_datatype();
found = true; if (callee_type.is_set() && !callee_type.is_variant()) {
if (callee_type.builtin_type == Variant::CALLABLE) { found = true;
push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee); if (callee_type.builtin_type == Variant::CALLABLE) {
} else { push_error(vformat(R"*(Name "%s" is a Callable. You can call it with "%s.call()" instead.)*", p_call->function_name, p_call->function_name), p_call->callee);
push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee); } else {
push_error(vformat(R"*(Name "%s" called as a function but is a "%s".)*", p_call->function_name, callee_type.to_string()), p_call->callee);
}
#ifdef DEBUG_ENABLED
} else if (!is_self) {
parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string());
mark_node_unsafe(p_call);
#endif
} }
} else if (!is_self) {
parser->push_warning(p_call, GDScriptWarning::UNSAFE_METHOD_ACCESS, p_call->function_name, base_type.to_string());
mark_node_unsafe(p_call);
} }
} }
if (!found && is_self) { if (!found && is_self) {
@ -1685,33 +1809,32 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
return; return;
} }
cast_type.is_meta_type = false; // The casted value won't be a type name.
p_cast->set_datatype(cast_type); p_cast->set_datatype(cast_type);
if (!cast_type.is_variant()) { if (!cast_type.is_variant()) {
if (!cast_type.is_meta_type) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
push_error(vformat(R"(Cast type "%s" isn't a valid type.)", cast_type.to_string()), p_cast->cast_type); if (!op_type.is_variant()) {
} else { bool valid = false;
cast_type.is_meta_type = false; // For compatibility check purpose. if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
}
GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!valid) {
if (!op_type.is_variant()) { push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
bool valid = false;
if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
}
if (!valid) {
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
}
} }
} }
} else {
mark_node_unsafe(p_cast);
} }
#ifdef DEBUG_ENABLED
if (p_cast->operand->get_datatype().is_variant()) { if (p_cast->operand->get_datatype().is_variant()) {
parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string()); parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
mark_node_unsafe(p_cast); mark_node_unsafe(p_cast);
} }
#endif
// TODO: Perform cast on constants. // TODO: Perform cast on constants.
} }
@ -1721,7 +1844,9 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
for (int i = 0; i < p_dictionary->elements.size(); i++) { for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
reduce_expression(element.key); if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) {
reduce_expression(element.key);
}
reduce_expression(element.value); reduce_expression(element.value);
all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
} }
@ -1770,7 +1895,9 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str
type.builtin_type = Variant::OBJECT; type.builtin_type = Variant::OBJECT;
type.native_type = ScriptServer::get_global_class_native_base(p_class_name); type.native_type = ScriptServer::get_global_class_native_base(p_class_name);
type.class_type = ref->get_parser()->head; type.class_type = ref->get_parser()->head;
type.script_path = ref->get_parser()->script_path;
type.is_constant = true; type.is_constant = true;
type.is_meta_type = true;
return type; return type;
} }
@ -1837,6 +1964,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
while (base_class != nullptr) { while (base_class != nullptr) {
if (base_class->identifier && base_class->identifier->name == name) {
p_identifier->set_datatype(base_class->get_datatype());
return;
}
if (base_class->has_member(name)) { if (base_class->has_member(name)) {
const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); const GDScriptParser::ClassNode::Member &member = base_class->get_member(name);
p_identifier->set_datatype(member.get_datatype()); p_identifier->set_datatype(member.get_datatype());
@ -2095,8 +2226,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
} else { } else {
if (base_type.kind == GDScriptParser::DataType::BUILTIN) { if (base_type.kind == GDScriptParser::DataType::BUILTIN) {
push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute);
#ifdef DEBUG_ENABLED
} else { } else {
parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string());
#endif
} }
result_type.kind = GDScriptParser::DataType::VARIANT; result_type.kind = GDScriptParser::DataType::VARIANT;
} }
@ -2283,7 +2416,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
GDScriptParser::DataType result; GDScriptParser::DataType result;
if (p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr->is_constant) { if (p_ternary_op->condition && p_ternary_op->condition->is_constant && p_ternary_op->true_expr->is_constant && p_ternary_op->false_expr && p_ternary_op->false_expr->is_constant) {
p_ternary_op->is_constant = true; p_ternary_op->is_constant = true;
if (p_ternary_op->condition->reduced_value.booleanize()) { if (p_ternary_op->condition->reduced_value.booleanize()) {
p_ternary_op->reduced_value = p_ternary_op->true_expr->reduced_value; p_ternary_op->reduced_value = p_ternary_op->true_expr->reduced_value;
@ -2292,8 +2425,18 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
} }
} }
GDScriptParser::DataType true_type = p_ternary_op->true_expr->get_datatype(); GDScriptParser::DataType true_type;
GDScriptParser::DataType false_type = p_ternary_op->false_expr->get_datatype(); if (p_ternary_op->true_expr) {
true_type = p_ternary_op->true_expr->get_datatype();
} else {
true_type.kind = GDScriptParser::DataType::VARIANT;
}
GDScriptParser::DataType false_type;
if (p_ternary_op->false_expr) {
false_type = p_ternary_op->false_expr->get_datatype();
} else {
false_type.kind = GDScriptParser::DataType::VARIANT;
}
if (true_type.is_variant() || false_type.is_variant()) { if (true_type.is_variant() || false_type.is_variant()) {
result.kind = GDScriptParser::DataType::VARIANT; result.kind = GDScriptParser::DataType::VARIANT;
@ -2304,8 +2447,9 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
if (!is_type_compatible(false_type, true_type)) { if (!is_type_compatible(false_type, true_type)) {
result.type_source = GDScriptParser::DataType::UNDETECTED; result.type_source = GDScriptParser::DataType::UNDETECTED;
result.kind = GDScriptParser::DataType::VARIANT; result.kind = GDScriptParser::DataType::VARIANT;
#ifdef DEBUG_ENABLED
parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY); parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY);
#endif
} }
} }
} }
@ -2360,12 +2504,14 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} }
if (scr.is_valid()) { if (scr.is_valid()) {
result.script_type = scr; result.script_type = scr;
result.script_path = scr->get_path();
Ref<GDScript> gds = scr; Ref<GDScript> gds = scr;
if (gds.is_valid()) { if (gds.is_valid()) {
result.kind = GDScriptParser::DataType::CLASS; result.kind = GDScriptParser::DataType::CLASS;
Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path()); Ref<GDScriptParserRef> ref = get_parser_for(gds->get_path());
ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED);
result.class_type = ref->get_parser()->head; result.class_type = ref->get_parser()->head;
result.script_path = ref->get_parser()->script_path;
} else { } else {
result.kind = GDScriptParser::DataType::SCRIPT; result.kind = GDScriptParser::DataType::SCRIPT;
} }
@ -2410,6 +2556,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
r_static = false; r_static = false;
r_vararg = false; r_vararg = false;
r_default_arg_count = 0; r_default_arg_count = 0;
StringName function_name = p_function;
if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
// Construct a base type to get methods. // Construct a base type to get methods.
@ -2430,23 +2577,29 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
return false; return false;
} }
bool is_constructor = p_base_type.is_meta_type && p_function == "new";
if (is_constructor) {
function_name = "_init";
r_static = true;
}
GDScriptParser::ClassNode *base_class = p_base_type.class_type; GDScriptParser::ClassNode *base_class = p_base_type.class_type;
GDScriptParser::FunctionNode *found_function = nullptr; GDScriptParser::FunctionNode *found_function = nullptr;
while (found_function == nullptr && base_class != nullptr) { while (found_function == nullptr && base_class != nullptr) {
if (base_class->has_member(p_function)) { if (base_class->has_member(function_name)) {
if (base_class->get_member(p_function).type != GDScriptParser::ClassNode::Member::FUNCTION) { if (base_class->get_member(function_name).type != GDScriptParser::ClassNode::Member::FUNCTION) {
// TODO: If this is Callable it can have a better error message. // TODO: If this is Callable it can have a better error message.
push_error(vformat(R"(Member "%s" is not a function.)", p_function), p_source); push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source);
return false; return false;
} }
found_function = base_class->get_member(p_function).function; found_function = base_class->get_member(function_name).function;
} }
base_class = base_class->base_type.class_type; base_class = base_class->base_type.class_type;
} }
if (found_function != nullptr) { if (found_function != nullptr) {
r_static = found_function->is_static; r_static = is_constructor || found_function->is_static;
for (int i = 0; i < found_function->parameters.size(); i++) { for (int i = 0; i < found_function->parameters.size(); i++) {
r_par_types.push_back(found_function->parameters[i]->get_datatype()); r_par_types.push_back(found_function->parameters[i]->get_datatype());
if (found_function->parameters[i]->default_value != nullptr) { if (found_function->parameters[i]->default_value != nullptr) {
@ -2459,12 +2612,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
return true; return true;
} }
// TODO: Check here the "new" function in GDScript class when metatype, as it needs to look the constructor.
Ref<Script> base_script = p_base_type.script_type; Ref<Script> base_script = p_base_type.script_type;
while (base_script.is_valid() && base_script->is_valid()) { while (base_script.is_valid() && base_script->is_valid()) {
MethodInfo info = base_script->get_method_info(p_function); MethodInfo info = base_script->get_method_info(function_name);
if (!(info == MethodInfo())) { if (!(info == MethodInfo())) {
return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
@ -2473,32 +2624,38 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD
} }
// If the base is a script, it might be trying to access members of the Script class itself. // If the base is a script, it might be trying to access members of the Script class itself.
if (p_base_type.is_meta_type && !(p_function == "new") && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) { if (p_base_type.is_meta_type && !is_constructor && (p_base_type.kind == GDScriptParser::DataType::SCRIPT || p_base_type.kind == GDScriptParser::DataType::CLASS)) {
MethodInfo info; MethodInfo info;
StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static()); StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static());
if (ClassDB::get_method_info(script_class, p_function, &info)) { if (ClassDB::get_method_info(script_class, function_name, &info)) {
return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
} }
} }
StringName base_native = p_base_type.native_type; StringName base_native = p_base_type.native_type;
if (base_native == StringName()) {
// Empty native class, might happen in some Script implementations.
// Just ignore it.
return false;
}
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
if (!class_exists(base_native)) { if (base_native != StringName()) {
ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native)); // Empty native class might happen in some Script implementations.
// Just ignore it.
if (!class_exists(base_native)) {
ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native));
}
} }
#endif #endif
if (is_constructor) {
// Native types always have a default constructor.
r_return_type = p_base_type;
r_return_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
r_return_type.is_meta_type = false;
return true;
}
StringName real_native = get_real_class_name(base_native); StringName real_native = get_real_class_name(base_native);
MethodInfo info; MethodInfo info;
if (ClassDB::get_method_info(real_native, p_function, &info)) { if (ClassDB::get_method_info(real_native, function_name, &info)) {
return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg);
} }
@ -2558,15 +2715,18 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
p_call->arguments[i]); p_call->arguments[i]);
valid = false; valid = false;
} }
#ifdef DEBUG_ENABLED
} else { } else {
if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
} }
#endif
} }
} }
return valid; return valid;
} }
#ifdef DEBUG_ENABLED
bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) { bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context) {
const StringName &name = p_local->name; const StringName &name = p_local->name;
GDScriptParser::DataType base = parser->current_class->get_datatype(); GDScriptParser::DataType base = parser->current_class->get_datatype();
@ -2609,6 +2769,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con
return false; return false;
} }
#endif
GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) { GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid) {
// This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations. // This function creates dummy variant values and apply the operation to those. Less error-prone than keeping a table of valid operations.
@ -2835,9 +2996,11 @@ void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser:
} }
void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) {
#ifdef DEBUG_ENABLED
for (int i = p_node->start_line; i <= p_node->end_line; i++) { for (int i = p_node->start_line; i <= p_node->end_line; i++) {
parser->unsafe_lines.insert(i); parser->unsafe_lines.insert(i);
} }
#endif
} }
bool GDScriptAnalyzer::class_exists(const StringName &p_class) { bool GDScriptAnalyzer::class_exists(const StringName &p_class) {
@ -2851,7 +3014,7 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
ref = depended_parsers[p_path]; ref = depended_parsers[p_path];
} else { } else {
Error err = OK; Error err = OK;
ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err); ref = GDScriptCache::get_parser(p_path, GDScriptParserRef::EMPTY, err, parser->script_path);
depended_parsers[p_path] = ref; depended_parsers[p_path] = ref;
} }

View File

@ -96,13 +96,15 @@ class GDScriptAnalyzer {
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
void mark_node_unsafe(const GDScriptParser::Node *p_node); void mark_node_unsafe(const GDScriptParser::Node *p_node);
bool class_exists(const StringName &p_class); bool class_exists(const StringName &p_class);
Ref<GDScriptParserRef> get_parser_for(const String &p_path); Ref<GDScriptParserRef> get_parser_for(const String &p_path);
#ifdef DEBUG_ENABLED
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
public: public:
Error resolve_inheritance(); Error resolve_inheritance();

View File

@ -109,9 +109,12 @@ void GDScriptCache::remove_script(const String &p_path) {
singleton->full_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path);
} }
Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error) { Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) {
MutexLock(singleton->lock); MutexLock(singleton->lock);
Ref<GDScriptParserRef> ref; Ref<GDScriptParserRef> ref;
if (p_owner != String()) {
singleton->dependencies[p_owner].insert(p_path);
}
if (singleton->parser_map.has(p_path)) { if (singleton->parser_map.has(p_path)) {
ref = singleton->parser_map[p_path]; ref = singleton->parser_map[p_path];
} else { } else {
@ -120,7 +123,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
ref->parser = parser; ref->parser = parser;
ref->path = p_path; ref->path = p_path;
singleton->parser_map[p_path] = ref; singleton->parser_map[p_path] = ref;
ref.unref(); ref->unreference();
} }
r_error = ref->raise_status(p_status); r_error = ref->raise_status(p_status);
@ -164,7 +167,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri
Ref<GDScript> script; Ref<GDScript> script;
script.instance(); script.instance();
script->set_path(p_path); script->set_path(p_path, true);
script->set_script_path(p_path); script->set_script_path(p_path);
script->load_source_code(p_path); script->load_source_code(p_path);
@ -188,9 +191,15 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
} }
Ref<GDScript> script = get_shallow_script(p_path); Ref<GDScript> script = get_shallow_script(p_path);
r_error = script->load_source_code(p_path);
if (r_error) {
return script;
}
r_error = script->reload(); r_error = script->reload();
if (r_error) { if (r_error) {
return Ref<GDScript>(); return script;
} }
singleton->full_gdscript_cache[p_path] = script; singleton->full_gdscript_cache[p_path] = script;
@ -200,6 +209,11 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
} }
Error GDScriptCache::finish_compiling(const String &p_owner) { Error GDScriptCache::finish_compiling(const String &p_owner) {
// Mark this as compiled.
Ref<GDScript> script = get_shallow_script(p_owner);
singleton->full_gdscript_cache[p_owner] = script;
singleton->shallow_gdscript_cache.erase(p_owner);
Set<String> depends = singleton->dependencies[p_owner]; Set<String> depends = singleton->dependencies[p_owner];
Error err = OK; Error err = OK;
@ -213,6 +227,8 @@ Error GDScriptCache::finish_compiling(const String &p_owner) {
} }
} }
singleton->dependencies.erase(p_owner);
return err; return err;
} }

View File

@ -84,7 +84,7 @@ class GDScriptCache {
static void remove_script(const String &p_path); static void remove_script(const String &p_path);
public: public:
static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error); static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String());
static String get_source_code(const String &p_path); static String get_source_code(const String &p_path);
static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String());
static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String());

View File

@ -31,6 +31,7 @@
#include "gdscript_compiler.h" #include "gdscript_compiler.h"
#include "gdscript.h" #include "gdscript.h"
#include "gdscript_cache.h"
bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) {
if (codegen.function_node && codegen.function_node->is_static) { if (codegen.function_node && codegen.function_node->is_static) {
@ -114,7 +115,7 @@ bool GDScriptCompiler::_create_binary_operator(CodeGen &codegen, const GDScriptP
} }
GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const { GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const {
if (!p_datatype.is_set()) { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) {
return GDScriptDataType(); return GDScriptDataType();
} }
@ -122,6 +123,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
result.has_type = true; result.has_type = true;
switch (p_datatype.kind) { switch (p_datatype.kind) {
case GDScriptParser::DataType::VARIANT: {
result.has_type = false;
} break;
case GDScriptParser::DataType::BUILTIN: { case GDScriptParser::DataType::BUILTIN: {
result.kind = GDScriptDataType::BUILTIN; result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = p_datatype.builtin_type; result.builtin_type = p_datatype.builtin_type;
@ -139,38 +143,48 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
// Locate class by constructing the path to it and following that path // Locate class by constructing the path to it and following that path
GDScriptParser::ClassNode *class_type = p_datatype.class_type; GDScriptParser::ClassNode *class_type = p_datatype.class_type;
if (class_type) { if (class_type) {
List<StringName> names; if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.empty() && class_type->fqcn.begins_with(main_script->name))) {
while (class_type->outer) { // Local class.
names.push_back(class_type->identifier->name); List<StringName> names;
class_type = class_type->outer; while (class_type->outer) {
} names.push_back(class_type->identifier->name);
class_type = class_type->outer;
Ref<GDScript> script = Ref<GDScript>(main_script);
while (names.back()) {
if (!script->subclasses.has(names.back()->get())) {
ERR_PRINT("Parser bug: Cannot locate datatype class.");
result.has_type = false;
return GDScriptDataType();
} }
script = script->subclasses[names.back()->get()];
names.pop_back();
}
result.kind = GDScriptDataType::GDSCRIPT;
result.script_type = script;
result.native_type = script->get_instance_base_type();
} else {
result.kind = GDScriptDataType::GDSCRIPT;
result.script_type = p_datatype.script_type;
result.native_type = result.script_type->get_instance_base_type();
}
Ref<GDScript> script = Ref<GDScript>(main_script);
while (names.back()) {
if (!script->subclasses.has(names.back()->get())) {
ERR_PRINT("Parser bug: Cannot locate datatype class.");
result.has_type = false;
return GDScriptDataType();
}
script = script->subclasses[names.back()->get()];
names.pop_back();
}
result.kind = GDScriptDataType::GDSCRIPT;
result.script_type = script;
result.native_type = script->get_instance_base_type();
} else {
result.kind = GDScriptDataType::GDSCRIPT;
result.script_type = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path);
result.native_type = p_datatype.native_type;
}
}
} break; } break;
case GDScriptParser::DataType::ENUM_VALUE:
result.has_type = true;
result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = Variant::INT;
break;
case GDScriptParser::DataType::ENUM:
result.has_type = true;
result.kind = GDScriptDataType::BUILTIN;
result.builtin_type = Variant::DICTIONARY;
break;
case GDScriptParser::DataType::UNRESOLVED: { case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type."); ERR_PRINT("Parser bug: converting unresolved type.");
return GDScriptDataType(); return GDScriptDataType();
} }
default:
break; // FIXME
} }
return result; return result;
@ -234,6 +248,66 @@ int GDScriptCompiler::_parse_assign_right_expression(CodeGen &codegen, const GDS
return dst_addr; return dst_addr;
} }
bool GDScriptCompiler::_generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type) {
if (p_datatype.has_type && p_value_type.is_variant()) {
// Typed assignment
switch (p_datatype.kind) {
case GDScriptDataType::BUILTIN: {
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
codegen.opcodes.push_back(p_datatype.builtin_type); // variable type
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2
} break;
case GDScriptDataType::NATIVE: {
int class_idx;
if (GDScriptLanguage::get_singleton()->get_global_map().has(p_datatype.native_type)) {
class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_datatype.native_type];
class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
} else {
// _set_error("Invalid native class type '" + String(p_datatype.native_type) + "'.", on->arguments[0]);
return false;
}
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator
codegen.opcodes.push_back(class_idx); // variable type
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2
} break;
case GDScriptDataType::SCRIPT:
case GDScriptDataType::GDSCRIPT: {
Variant script = p_datatype.script_type;
int idx = codegen.get_constant_pos(script); //make it a local constant (faster access)
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
codegen.opcodes.push_back(idx); // variable type
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2
} break;
default: {
ERR_PRINT("Compiler bug: unresolved assign.");
// Shouldn't get here, but fail-safe to a regular assignment
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter)
}
}
} else {
if (p_datatype.kind == GDScriptDataType::BUILTIN && p_value_type.kind == GDScriptParser::DataType::BUILTIN && p_datatype.builtin_type != p_value_type.builtin_type) {
// Need conversion.
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
codegen.opcodes.push_back(p_datatype.builtin_type); // variable type
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2
} else {
// Either untyped assignment or already type-checked by the parser
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
codegen.opcodes.push_back(p_dst_address); // argument 1
codegen.opcodes.push_back(p_src_address); // argument 2 (unary only takes one parameter)
}
}
return true;
}
int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) { int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_expression, int p_stack_level, bool p_root, bool p_initializer, int p_index_addr) {
if (p_expression->is_constant) { if (p_expression->is_constant) {
return codegen.get_constant_pos(p_expression->reduced_value); return codegen.get_constant_pos(p_expression->reduced_value);
@ -368,15 +442,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
class_node = class_node->outer; class_node = class_node->outer;
} }
if (class_node->identifier && class_node->identifier->name == identifier) { RES res;
_set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression);
return -1;
}
RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); if (class_node->identifier && class_node->identifier->name == identifier) {
if (res.is_null()) { res = Ref<GDScript>(main_script);
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); } else {
return -1; res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
if (res.is_null()) {
_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
return -1;
}
} }
Variant key = res; Variant key = res;
@ -534,8 +609,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.alloc_stack(slevel); codegen.alloc_stack(slevel);
} }
// FIXME: Allow actual cast. GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
GDScriptDataType cast_type; // = _gdtype_from_datatype(cn->cast_type);
switch (cast_type.kind) { switch (cast_type.kind) {
case GDScriptDataType::BUILTIN: { case GDScriptDataType::BUILTIN: {
@ -685,8 +759,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
arguments.push_back(ret); arguments.push_back(ret);
arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name)); arguments.push_back(codegen.get_name_map_pos(subscript->attribute->name));
} else { } else {
// TODO: Validate this at parse time.
// TODO: Though might not be the case if callables are a thing.
_set_error("Cannot call something that isn't a function.", call->callee); _set_error("Cannot call something that isn't a function.", call->callee);
return -1; return -1;
} }
@ -1329,55 +1401,6 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype());
if (assign_type.has_type && !assignment->assigned_value->get_datatype().is_variant()) {
// Typed assignment
switch (assign_type.kind) {
case GDScriptDataType::BUILTIN: {
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); // perform operator
codegen.opcodes.push_back(assign_type.builtin_type); // variable type
codegen.opcodes.push_back(dst_address_a); // argument 1
codegen.opcodes.push_back(src_address_b); // argument 2
} break;
case GDScriptDataType::NATIVE: {
int class_idx;
if (GDScriptLanguage::get_singleton()->get_global_map().has(assign_type.native_type)) {
class_idx = GDScriptLanguage::get_singleton()->get_global_map()[assign_type.native_type];
class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
} else {
// _set_error("Invalid native class type '" + String(assign_type.native_type) + "'.", on->arguments[0]);
return -1;
}
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); // perform operator
codegen.opcodes.push_back(class_idx); // variable type
codegen.opcodes.push_back(dst_address_a); // argument 1
codegen.opcodes.push_back(src_address_b); // argument 2
} break;
case GDScriptDataType::SCRIPT:
case GDScriptDataType::GDSCRIPT: {
Variant script = assign_type.script_type;
int idx = codegen.get_constant_pos(script); //make it a local constant (faster access)
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); // perform operator
codegen.opcodes.push_back(idx); // variable type
codegen.opcodes.push_back(dst_address_a); // argument 1
codegen.opcodes.push_back(src_address_b); // argument 2
} break;
default: {
ERR_PRINT("Compiler bug: unresolved assign.");
// Shouldn't get here, but fail-safe to a regular assignment
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
codegen.opcodes.push_back(dst_address_a); // argument 1
codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
}
}
} else {
// Either untyped assignment or already type-checked by the parser
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); // perform operator
codegen.opcodes.push_back(dst_address_a); // argument 1
codegen.opcodes.push_back(src_address_b); // argument 2 (unary only takes one parameter)
}
if (has_setter && !is_in_setter) { if (has_setter && !is_in_setter) {
// Call setter. // Call setter.
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL);
@ -1387,6 +1410,8 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
codegen.opcodes.push_back(dst_address_a); // Argument. codegen.opcodes.push_back(dst_address_a); // Argument.
codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here). codegen.opcodes.push_back(dst_address_a); // Result address (won't be used here).
codegen.alloc_call(1); codegen.alloc_call(1);
} else if (!_generate_typed_assign(codegen, src_address_b, dst_address_a, assign_type, assignment->assigned_value->get_datatype())) {
return -1;
} }
return dst_address_a; //if anything, returns wathever was assigned or correct stack position return dst_address_a; //if anything, returns wathever was assigned or correct stack position
@ -1662,7 +1687,7 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar
// Evaluate element by element. // Evaluate element by element.
for (int i = 0; i < p_pattern->dictionary.size(); i++) { for (int i = 0; i < p_pattern->dictionary.size(); i++) {
const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i]; const GDScriptParser::PatternNode::Pair &element = p_pattern->dictionary[i];
if (element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) { if (element.value_pattern && element.value_pattern->pattern_type == GDScriptParser::PatternNode::PT_REST) {
// Ignore rest pattern. // Ignore rest pattern.
continue; continue;
} }
@ -1698,28 +1723,30 @@ Error GDScriptCompiler::_parse_match_pattern(CodeGen &codegen, const GDScriptPar
r_patch_addresses.push_back(codegen.opcodes.size()); r_patch_addresses.push_back(codegen.opcodes.size());
codegen.opcodes.push_back(0); // Will be replaced. codegen.opcodes.push_back(0); // Will be replaced.
// Get actual value from user dictionary. if (element.value_pattern != nullptr) {
int value_addr = stlevel++; // Get actual value from user dictionary.
codegen.alloc_stack(stlevel); int value_addr = stlevel++;
codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET); codegen.alloc_stack(stlevel);
codegen.opcodes.push_back(p_value_addr); // Source. codegen.opcodes.push_back(GDScriptFunction::OPCODE_GET);
codegen.opcodes.push_back(pattern_key_addr); // Index. codegen.opcodes.push_back(p_value_addr); // Source.
codegen.opcodes.push_back(value_addr); // Destination. codegen.opcodes.push_back(pattern_key_addr); // Index.
codegen.opcodes.push_back(value_addr); // Destination.
// Also get type of value. // Also get type of value.
int value_type_addr = stlevel++; int value_type_addr = stlevel++;
value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS; value_type_addr |= GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS;
codegen.alloc_stack(stlevel); codegen.alloc_stack(stlevel);
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN); codegen.opcodes.push_back(GDScriptFunction::OPCODE_CALL_BUILT_IN);
codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF); codegen.opcodes.push_back(GDScriptFunctions::TYPE_OF);
codegen.opcodes.push_back(1); // One argument. codegen.opcodes.push_back(1); // One argument.
codegen.opcodes.push_back(value_addr); // Argument is the value we want to test. codegen.opcodes.push_back(value_addr); // Argument is the value we want to test.
codegen.opcodes.push_back(value_type_addr); // Address to result. codegen.opcodes.push_back(value_type_addr); // Address to result.
// Try the pattern inside the value. // Try the pattern inside the value.
Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches); Error err = _parse_match_pattern(codegen, element.value_pattern, stlevel, value_addr, value_type_addr, r_bound_variables, r_patch_addresses, element_block_patches);
if (err != OK) { if (err != OK) {
return err; return err;
}
} }
// Patch jumps to block to try the next element. // Patch jumps to block to try the next element.
@ -2055,20 +2082,19 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
if (src_address < 0) { if (src_address < 0) {
return ERR_PARSE_ERROR; return ERR_PARSE_ERROR;
} }
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(lv->get_datatype()), lv->initializer->get_datatype())) {
codegen.opcodes.push_back(dst_address); return ERR_PARSE_ERROR;
codegen.opcodes.push_back(src_address); }
} }
} break; } break;
case GDScriptParser::Node::CONSTANT: { case GDScriptParser::Node::CONSTANT: {
// Local constants. // Local constants.
const GDScriptParser::ConstantNode *lc = static_cast<const GDScriptParser::ConstantNode *>(s); const GDScriptParser::ConstantNode *lc = static_cast<const GDScriptParser::ConstantNode *>(s);
// FIXME: Need to properly reduce expressions to avoid limiting this so much. if (!lc->initializer->is_constant) {
if (lc->initializer->type != GDScriptParser::Node::LITERAL) { _set_error("Local constant must have a constant value as initializer.", lc->initializer);
_set_error("Local constant must have a literal as initializer.", lc->initializer);
return ERR_PARSE_ERROR; return ERR_PARSE_ERROR;
} }
codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(static_cast<const GDScriptParser::LiteralNode *>(lc->initializer)->value); codegen.local_named_constants[lc->identifier->name] = codegen.get_constant_pos(lc->initializer->reduced_value);
} break; } break;
case GDScriptParser::Node::PASS: case GDScriptParser::Node::PASS:
// Nothing to do. // Nothing to do.
@ -2158,9 +2184,9 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
int dst_address = codegen.script->member_indices[field->identifier->name].index; int dst_address = codegen.script->member_indices[field->identifier->name].index;
dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) {
codegen.opcodes.push_back(dst_address); return ERR_PARSE_ERROR;
codegen.opcodes.push_back(src_address); }
} }
} }
} }
@ -2188,9 +2214,9 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
int dst_address = codegen.script->member_indices[field->identifier->name].index; int dst_address = codegen.script->member_indices[field->identifier->name].index;
dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS; dst_address |= GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS;
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); if (!_generate_typed_assign(codegen, src_address, dst_address, _gdtype_from_datatype(field->get_datatype()), field->initializer->get_datatype())) {
codegen.opcodes.push_back(dst_address); return ERR_PARSE_ERROR;
codegen.opcodes.push_back(src_address); }
} }
} }
} }
@ -2209,9 +2235,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (src_addr < 0) { if (src_addr < 0) {
return ERR_PARSE_ERROR; return ERR_PARSE_ERROR;
} }
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSIGN); int dst_addr = codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS);
codegen.opcodes.push_back(codegen.stack_identifiers[p_func->parameters[i]->identifier->name] | (GDScriptFunction::ADDR_TYPE_STACK_VARIABLE << GDScriptFunction::ADDR_BITS)); if (!_generate_typed_assign(codegen, src_addr, dst_addr, _gdtype_from_datatype(p_func->parameters[i]->get_datatype()), p_func->parameters[i]->default_value->get_datatype())) {
codegen.opcodes.push_back(src_addr); return ERR_PARSE_ERROR;
}
defarg_addr.push_back(codegen.opcodes.size()); defarg_addr.push_back(codegen.opcodes.size());
} }
defarg_addr.invert(); defarg_addr.invert();
@ -2247,12 +2274,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
if (p_func) { if (p_func) {
gdfunc->_static = p_func->is_static; gdfunc->_static = p_func->is_static;
gdfunc->rpc_mode = p_func->rpc_mode; gdfunc->rpc_mode = p_func->rpc_mode;
// FIXME: Add actual parameters types;
gdfunc->argument_types.resize(p_func->parameters.size()); gdfunc->argument_types.resize(p_func->parameters.size());
for (int i = 0; i < p_func->parameters.size(); i++) { for (int i = 0; i < p_func->parameters.size(); i++) {
gdfunc->argument_types.write[i] = GDScriptDataType(); //_gdtype_from_datatype(p_func->argument_types[i]); gdfunc->argument_types.write[i] = _gdtype_from_datatype(p_func->parameters[i]->get_datatype());
} }
gdfunc->return_type = GDScriptDataType(); //_gdtype_from_datatype(p_func->return_type); gdfunc->return_type = _gdtype_from_datatype(p_func->get_datatype());
} else { } else {
gdfunc->_static = false; gdfunc->_static = false;
gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; gdfunc->rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
@ -2432,7 +2458,7 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP
gdfunc->_static = false; gdfunc->_static = false;
gdfunc->rpc_mode = p_variable->rpc_mode; gdfunc->rpc_mode = p_variable->rpc_mode;
gdfunc->argument_types.resize(p_is_setter ? 1 : 0); gdfunc->argument_types.resize(p_is_setter ? 1 : 0);
gdfunc->return_type = GDScriptDataType(); // TODO gdfunc->return_type = _gdtype_from_datatype(p_variable->get_datatype());
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
gdfunc->arg_names = argnames; gdfunc->arg_names = argnames;
#endif #endif
@ -2581,7 +2607,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
Ref<GDScript> base = base_type.script_type; Ref<GDScript> base = base_type.script_type;
p_script->base = base; p_script->base = base;
p_script->_base = base.ptr(); p_script->_base = base.ptr();
p_script->member_indices = base->member_indices;
if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) {
if (!parsed_classes.has(p_script->_base)) { if (!parsed_classes.has(p_script->_base)) {
@ -2596,6 +2621,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
} }
} }
} }
p_script->member_indices = base->member_indices;
} break; } break;
default: { default: {
_set_error("Parser bug: invalid inheritance.", p_class); _set_error("Parser bug: invalid inheritance.", p_class);
@ -2633,8 +2660,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
break; break;
} }
minfo.rpc_mode = variable->rpc_mode; minfo.rpc_mode = variable->rpc_mode;
// FIXME: Types. minfo.data_type = _gdtype_from_datatype(variable->get_datatype());
// minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);
PropertyInfo prop_info = minfo.data_type; PropertyInfo prop_info = minfo.data_type;
prop_info.name = name; prop_info.name = name;
@ -2965,7 +2991,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
return err; return err;
} }
return OK; return GDScriptCache::finish_compiling(p_script->get_path());
} }
String GDScriptCompiler::get_error() const { String GDScriptCompiler::get_error() const {

View File

@ -146,6 +146,7 @@ class GDScriptCompiler {
bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level); bool _create_unary_operator(CodeGen &codegen, const GDScriptParser::UnaryOpNode *on, Variant::Operator op, int p_stack_level);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0); bool _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, int p_stack_level, bool p_initializer = false, int p_index_addr = 0);
bool _generate_typed_assign(CodeGen &codegen, int p_src_address, int p_dst_address, const GDScriptDataType &p_datatype, const GDScriptParser::DataType &p_value_type);
GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const; GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype) const;

View File

@ -171,6 +171,7 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &
} }
} }
#ifdef DEBUG_ENABLED
if (r_safe_lines) { if (r_safe_lines) {
const Set<int> &unsafe_lines = parser.get_unsafe_lines(); const Set<int> &unsafe_lines = parser.get_unsafe_lines();
for (int i = 1; i <= parser.get_last_line_number(); i++) { for (int i = 1; i <= parser.get_last_line_number(); i++) {
@ -179,6 +180,7 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &
} }
} }
} }
#endif
return true; return true;
} }
@ -1994,8 +1996,8 @@ static void _find_last_return_in_block(GDScriptParser::CompletionContext &p_cont
} break; } break;
case GDScriptParser::Node::MATCH: { case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]); const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(p_context.current_suite->statements[i]);
for (int i = 0; i < match->branches.size(); i++) { for (int j = 0; j < match->branches.size(); j++) {
c.current_suite = match->branches[i]->block; c.current_suite = match->branches[j]->block;
_find_last_return_in_block(c, r_last_return_line, r_last_returned_value); _find_last_return_in_block(c, r_last_return_line, r_last_returned_value);
} }
} break; } break;

View File

@ -151,24 +151,6 @@ GDScriptParser::~GDScriptParser() {
clear(); clear();
} }
template <class T>
T *GDScriptParser::alloc_node() {
T *node = memnew(T);
node->next = list;
list = node;
// TODO: Properly set positions for all nodes.
node->start_line = previous.start_line;
node->end_line = previous.end_line;
node->start_column = previous.start_column;
node->end_column = previous.end_column;
node->leftmost_column = previous.leftmost_column;
node->rightmost_column = previous.rightmost_column;
return node;
}
void GDScriptParser::clear() { void GDScriptParser::clear() {
while (list != nullptr) { while (list != nullptr) {
Node *element = list; Node *element = list;
@ -196,6 +178,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
} }
} }
#ifdef DEBUG_ENABLED
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
Vector<String> symbols; Vector<String> symbols;
if (!p_symbol1.empty()) { if (!p_symbol1.empty()) {
@ -250,6 +233,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
warnings.push_front(warning); warnings.push_front(warning);
} }
} }
#endif
void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
@ -542,6 +526,7 @@ void GDScriptParser::parse_program() {
push_error(R"("extends" can only be used once.)"); push_error(R"("extends" can only be used once.)");
} else { } else {
parse_extends(); parse_extends();
end_statement("superclass");
} }
break; break;
default: default:
@ -589,7 +574,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() {
n_class->identifier = parse_identifier(); n_class->identifier = parse_identifier();
} }
if (check(GDScriptTokenizer::Token::EXTENDS)) { if (match(GDScriptTokenizer::Token::EXTENDS)) {
parse_extends(); parse_extends();
} }
@ -628,6 +613,7 @@ void GDScriptParser::parse_class_name() {
if (match(GDScriptTokenizer::Token::EXTENDS)) { if (match(GDScriptTokenizer::Token::EXTENDS)) {
// Allow extends on the same line. // Allow extends on the same line.
parse_extends(); parse_extends();
end_statement("superclass");
} else { } else {
end_statement("class_name statement"); end_statement("class_name statement");
} }
@ -664,8 +650,6 @@ void GDScriptParser::parse_extends() {
} }
current_class->extends.push_back(previous.literal); current_class->extends.push_back(previous.literal);
} }
end_statement("superclass");
} }
template <class T> template <class T>
@ -692,7 +676,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
// Enums may be unnamed. // Enums may be unnamed.
// TODO: Consider names in outer scope too, for constants and classes (and static functions?) // TODO: Consider names in outer scope too, for constants and classes (and static functions?)
if (current_class->members_indices.has(member->identifier->name)) { if (current_class->members_indices.has(member->identifier->name)) {
push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name())); push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier);
} else { } else {
current_class->add_member(member); current_class->add_member(member);
} }
@ -731,6 +715,7 @@ void GDScriptParser::parse_class_body() {
break; break;
} }
case GDScriptTokenizer::Token::PASS: case GDScriptTokenizer::Token::PASS:
advance();
end_statement(R"("pass")"); end_statement(R"("pass")");
break; break;
case GDScriptTokenizer::Token::DEDENT: case GDScriptTokenizer::Token::DEDENT:
@ -1046,7 +1031,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
EnumNode::Value item; EnumNode::Value item;
item.identifier = parse_identifier(); item.identifier = parse_identifier();
bool found = false;
if (!named) { if (!named) {
// TODO: Abstract this recursive member check. // TODO: Abstract this recursive member check.
@ -1054,7 +1038,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
while (parent != nullptr) { while (parent != nullptr) {
if (parent->members_indices.has(item.identifier->name)) { if (parent->members_indices.has(item.identifier->name)) {
push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name())); push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name()));
found = true;
break; break;
} }
parent = parent->outer; parent = parent->outer;
@ -1193,10 +1176,10 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
push_completion_call(annotation); push_completion_call(annotation);
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
int argument = 0; int argument_index = 0;
do { do {
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument, true); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
set_last_completion_call_arg(argument++, true); set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false); ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) { if (argument == nullptr) {
valid = false; valid = false;
@ -1313,7 +1296,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
GDScriptParser::Node *GDScriptParser::parse_statement() { GDScriptParser::Node *GDScriptParser::parse_statement() {
Node *result = nullptr; Node *result = nullptr;
#ifdef DEBUG_ENABLED
bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code; bool unreachable = current_suite->has_return && !current_suite->has_unreachable_code;
#endif
switch (current.type) { switch (current.type) {
case GDScriptTokenizer::Token::PASS: case GDScriptTokenizer::Token::PASS:
@ -1357,6 +1342,9 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
advance(); advance();
ReturnNode *n_return = alloc_node<ReturnNode>(); ReturnNode *n_return = alloc_node<ReturnNode>();
if (!is_statement_end()) { if (!is_statement_end()) {
if (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) {
push_error(R"(Constructor cannot return a value.)");
}
n_return->return_value = parse_expression(false); n_return->return_value = parse_expression(false);
} }
result = n_return; result = n_return;
@ -1392,6 +1380,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
end_statement("expression"); end_statement("expression");
result = expression; result = expression;
#ifdef DEBUG_ENABLED
if (expression != nullptr) { if (expression != nullptr) {
switch (expression->type) { switch (expression->type) {
case Node::CALL: case Node::CALL:
@ -1403,14 +1392,17 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION); push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
} }
} }
#endif
break; break;
} }
} }
#ifdef DEBUG_ENABLED
if (unreachable) { if (unreachable) {
current_suite->has_unreachable_code = true; current_suite->has_unreachable_code = true;
push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name);
} }
#endif
if (panic_mode) { if (panic_mode) {
synchronize(); synchronize();
@ -1488,7 +1480,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
can_continue = true; can_continue = true;
SuiteNode *suite = alloc_node<SuiteNode>(); SuiteNode *suite = alloc_node<SuiteNode>();
suite->add_local(SuiteNode::Local(n_for->variable)); if (n_for->variable) {
suite->add_local(SuiteNode::Local(n_for->variable));
}
suite->parent_for = n_for; suite->parent_for = n_for;
n_for->loop = parse_suite(R"("for" block)", suite); n_for->loop = parse_suite(R"("for" block)", suite);
@ -1553,11 +1547,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
return match; return match;
} }
#ifdef DEBUG_ENABLED
bool all_have_return = true; bool all_have_return = true;
bool have_wildcard = false; bool have_wildcard = false;
bool wildcard_has_return = false; bool wildcard_has_return = false;
bool have_wildcard_without_continue = false; bool have_wildcard_without_continue = false;
bool have_unreachable_pattern = false; #endif
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
MatchBranchNode *branch = parse_match_branch(); MatchBranchNode *branch = parse_match_branch();
@ -1565,7 +1560,8 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
continue; continue;
} }
if (have_wildcard_without_continue && !have_unreachable_pattern) { #ifdef DEBUG_ENABLED
if (have_wildcard_without_continue) {
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN); push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
} }
@ -1581,14 +1577,17 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
if (!branch->block->has_return) { if (!branch->block->has_return) {
all_have_return = false; all_have_return = false;
} }
#endif
match->branches.push_back(branch); match->branches.push_back(branch);
} }
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)"); consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
#ifdef DEBUG_ENABLED
if (wildcard_has_return || (all_have_return && have_wildcard)) { if (wildcard_has_return || (all_have_return && have_wildcard)) {
current_suite->has_return = true; current_suite->has_return = true;
} }
#endif
return match; return match;
} }
@ -1740,7 +1739,7 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
if (key == nullptr) { if (key == nullptr) {
push_error(R"(Expected expression as key for dictionary pattern.)"); push_error(R"(Expected expression as key for dictionary pattern.)");
} }
if (consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after dictionary pattern key)")) { if (match(GDScriptTokenizer::Token::COLON)) {
// Value pattern. // Value pattern.
PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern);
if (sub_pattern == nullptr) { if (sub_pattern == nullptr) {
@ -1753,6 +1752,9 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_
} else { } else {
pattern->dictionary.push_back({ key, sub_pattern }); pattern->dictionary.push_back({ key, sub_pattern });
} }
} else {
// Key match only.
pattern->dictionary.push_back({ key, nullptr });
} }
} }
} while (match(GDScriptTokenizer::Token::COMMA)); } while (match(GDScriptTokenizer::Token::COMMA));
@ -1824,6 +1826,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
break; // Nothing to do. break; // Nothing to do.
} }
// Completion can appear whenever an expression is expected.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
GDScriptTokenizer::Token token = advance(); GDScriptTokenizer::Token token = advance();
ParseFunction prefix_rule = get_rule(token.type)->prefix; ParseFunction prefix_rule = get_rule(token.type)->prefix;
@ -1871,8 +1876,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>(); IdentifierNode *identifier = alloc_node<IdentifierNode>();
identifier->name = previous.get_identifier(); identifier->name = previous.get_identifier();
make_completion_context(COMPLETION_IDENTIFIER, identifier);
if (current_suite != nullptr && current_suite->has_local(identifier->name)) { if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
switch (declaration.type) { switch (declaration.type) {
@ -1925,7 +1928,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
} }
GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_previous_operand, bool p_can_assign) {
// FIXME: Don't allow "self" in a static context. if (!current_function || current_function->is_static) {
push_error(R"(Cannot use "self" outside a non-static function.)");
}
SelfNode *self = alloc_node<SelfNode>(); SelfNode *self = alloc_node<SelfNode>();
self->current_class = current_class; self->current_class = current_class;
return self; return self;
@ -2115,15 +2120,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
return parse_expression(false); // Return the following expression. return parse_expression(false); // Return the following expression.
} }
#ifdef DEBUG_ENABLED
VariableNode *source_variable = nullptr; VariableNode *source_variable = nullptr;
#endif
switch (p_previous_operand->type) { switch (p_previous_operand->type) {
case Node::IDENTIFIER: { case Node::IDENTIFIER: {
#ifdef DEBUG_ENABLED
// Get source to store assignment count. // Get source to store assignment count.
// Also remove one usage since assignment isn't usage. // Also remove one usage since assignment isn't usage.
IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand); IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand);
switch (id->source) { switch (id->source) {
case IdentifierNode::LOCAL_VARIABLE: case IdentifierNode::LOCAL_VARIABLE:
source_variable = id->variable_source; source_variable = id->variable_source;
id->variable_source->usages--; id->variable_source->usages--;
break; break;
@ -2140,8 +2149,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
default: default:
break; break;
} }
break; #endif
} } break;
case Node::SUBSCRIPT: case Node::SUBSCRIPT:
// Okay. // Okay.
break; break;
@ -2152,11 +2161,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
AssignmentNode *assignment = alloc_node<AssignmentNode>(); AssignmentNode *assignment = alloc_node<AssignmentNode>();
make_completion_context(COMPLETION_ASSIGN, assignment); make_completion_context(COMPLETION_ASSIGN, assignment);
#ifdef DEBUG_ENABLED
bool has_operator = true; bool has_operator = true;
#endif
switch (previous.type) { switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL: case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE; assignment->operation = AssignmentNode::OP_NONE;
#ifdef DEBUG_ENABLED
has_operator = false; has_operator = false;
#endif
break; break;
case GDScriptTokenizer::Token::PLUS_EQUAL: case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION; assignment->operation = AssignmentNode::OP_ADDITION;
@ -2194,9 +2207,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
assignment->assignee = p_previous_operand; assignment->assignee = p_previous_operand;
assignment->assigned_value = parse_expression(false); assignment->assigned_value = parse_expression(false);
#ifdef DEBUG_ENABLED
if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { if (has_operator && source_variable != nullptr && source_variable->assignments == 0) {
push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
} }
#endif
return assignment; return assignment;
} }
@ -2391,7 +2406,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
call->function_name = current_function->identifier->name; call->function_name = current_function->identifier->name;
} else { } else {
consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)"); consume(GDScriptTokenizer::Token::PERIOD, R"(Expected "." or "(" after "super".)");
make_completion_context(COMPLETION_SUPER_METHOD, call); make_completion_context(COMPLETION_SUPER_METHOD, call, true);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) { if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after ".".)")) {
pop_multiline(); pop_multiline();
return nullptr; return nullptr;
@ -2406,10 +2421,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
if (call->callee->type == Node::IDENTIFIER) { if (call->callee->type == Node::IDENTIFIER) {
call->function_name = static_cast<IdentifierNode *>(call->callee)->name; call->function_name = static_cast<IdentifierNode *>(call->callee)->name;
make_completion_context(COMPLETION_METHOD, call->callee);
} else if (call->callee->type == Node::SUBSCRIPT) { } else if (call->callee->type == Node::SUBSCRIPT) {
SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee); SubscriptNode *attribute = static_cast<SubscriptNode *>(call->callee);
if (attribute->is_attribute) { if (attribute->is_attribute) {
call->function_name = attribute->attribute->name; if (attribute->attribute) {
call->function_name = attribute->attribute->name;
}
make_completion_context(COMPLETION_ATTRIBUTE_METHOD, call->callee);
} else { } else {
// TODO: The analyzer can see if this is actually a Callable and give better error message. // TODO: The analyzer can see if this is actually a Callable and give better error message.
push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*"); push_error(R"*(Cannot call on an expression. Use ".call()" if it's a Callable.)*");
@ -2422,10 +2441,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Arguments. // Arguments.
push_completion_call(call); push_completion_call(call);
make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0); make_completion_context(COMPLETION_CALL_ARGUMENTS, call, 0, true);
int argument = 0; int argument_index = 0;
do { do {
make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument++); make_completion_context(COMPLETION_CALL_ARGUMENTS, call, argument_index++, true);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma. // Allow for trailing comma.
break; break;
@ -2543,8 +2562,8 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
if (!match(GDScriptTokenizer::Token::IDENTIFIER)) { if (!match(GDScriptTokenizer::Token::IDENTIFIER)) {
if (match(GDScriptTokenizer::Token::VOID)) { if (match(GDScriptTokenizer::Token::VOID)) {
if (p_allow_void) { if (p_allow_void) {
TypeNode *type = alloc_node<TypeNode>(); TypeNode *void_type = alloc_node<TypeNode>();
return type; return void_type;
} else { } else {
push_error(R"("void" is only allowed for a function return type.)"); push_error(R"("void" is only allowed for a function return type.)");
} }
@ -2649,6 +2668,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // SIGNAL, { nullptr, nullptr, PREC_NONE }, // SIGNAL,
{ nullptr, nullptr, PREC_NONE }, // STATIC, { nullptr, nullptr, PREC_NONE }, // STATIC,
{ &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER, { &GDScriptParser::parse_call, nullptr, PREC_NONE }, // SUPER,
{ nullptr, nullptr, PREC_NONE }, // TRAIT,
{ nullptr, nullptr, PREC_NONE }, // VAR, { nullptr, nullptr, PREC_NONE }, // VAR,
{ nullptr, nullptr, PREC_NONE }, // VOID, { nullptr, nullptr, PREC_NONE }, // VOID,
{ nullptr, nullptr, PREC_NONE }, // YIELD, { nullptr, nullptr, PREC_NONE }, // YIELD,
@ -2961,7 +2981,7 @@ String GDScriptParser::DataType::to_string() const {
if (!name.empty()) { if (!name.empty()) {
return name; return name;
} }
name = script_type->get_path(); name = script_path;
if (!name.empty()) { if (!name.empty()) {
return name; return name;
} }

View File

@ -42,12 +42,13 @@
#include "core/ustring.h" #include "core/ustring.h"
#include "core/variant.h" #include "core/variant.h"
#include "core/vector.h" #include "core/vector.h"
#include "gdscript_cache.h"
#include "gdscript_functions.h" #include "gdscript_functions.h"
#include "gdscript_tokenizer.h" #include "gdscript_tokenizer.h"
#include "gdscript_warning.h"
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
#include "core/string_builder.h" #include "core/string_builder.h"
#include "gdscript_warning.h"
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED
class GDScriptParser { class GDScriptParser {
@ -124,6 +125,7 @@ public:
StringName native_type; StringName native_type;
StringName enum_type; // Enum name or the value name in an enum. StringName enum_type; // Enum name or the value name in an enum.
Ref<Script> script_type; Ref<Script> script_type;
String script_path;
ClassNode *class_type = nullptr; ClassNode *class_type = nullptr;
MethodInfo method_info; // For callable/signals. MethodInfo method_info; // For callable/signals.
@ -131,7 +133,7 @@ public:
_FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; }
_FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; }
_FORCE_INLINE_ bool is_variant() const { return kind == VARIANT; } _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == UNRESOLVED; }
_FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; }
String to_string() const; String to_string() const;
@ -1019,12 +1021,14 @@ public:
COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint. COMPLETION_ANNOTATION_ARGUMENTS, // Annotation arguments hint.
COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values). COMPLETION_ASSIGN, // Assignment based on type (e.g. enum values).
COMPLETION_ATTRIBUTE, // After id.| to look for members. COMPLETION_ATTRIBUTE, // After id.| to look for members.
COMPLETION_ATTRIBUTE_METHOD, // After id.| to look for methods.
COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue). COMPLETION_BUILT_IN_TYPE_CONSTANT, // Constants inside a built-in type (e.g. Color.blue).
COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions). COMPLETION_CALL_ARGUMENTS, // Complete with nodes, input actions, enum values (or usual expressions).
// TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func). // TODO: COMPLETION_DECLARATION, // Potential declaration (var, const, func).
COMPLETION_GET_NODE, // Get node with $ notation. COMPLETION_GET_NODE, // Get node with $ notation.
COMPLETION_IDENTIFIER, // List available identifiers in scope. COMPLETION_IDENTIFIER, // List available identifiers in scope.
COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index. COMPLETION_INHERIT_TYPE, // Type after extends. Exclude non-viable types (built-ins, enums, void). Includes subtypes using the argument index.
COMPLETION_METHOD, // List available methods in scope.
COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals. COMPLETION_OVERRIDE_METHOD, // Override implementation, also for native virtuals.
COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set). COMPLETION_PROPERTY_DECLARATION, // Property declaration (get, set).
COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint. COMPLETION_PROPERTY_DECLARATION_OR_TYPE, // Property declaration (get, set) or a type hint.
@ -1047,6 +1051,7 @@ public:
Variant::Type builtin_type = Variant::VARIANT_MAX; Variant::Type builtin_type = Variant::VARIANT_MAX;
Node *node = nullptr; Node *node = nullptr;
Object *base = nullptr; Object *base = nullptr;
List<Ref<GDScriptParserRef>> dependent_parsers;
}; };
struct CompletionCall { struct CompletionCall {
@ -1069,9 +1074,11 @@ private:
ClassNode *head = nullptr; ClassNode *head = nullptr;
Node *list = nullptr; Node *list = nullptr;
List<ParserError> errors; List<ParserError> errors;
#ifdef DEBUG_ENABLED
List<GDScriptWarning> warnings; List<GDScriptWarning> warnings;
Set<String> ignored_warnings; Set<String> ignored_warnings;
Set<int> unsafe_lines; Set<int> unsafe_lines;
#endif
GDScriptTokenizer tokenizer; GDScriptTokenizer tokenizer;
GDScriptTokenizer::Token previous; GDScriptTokenizer::Token previous;
@ -1142,11 +1149,28 @@ private:
static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type); static ParseRule *get_rule(GDScriptTokenizer::Token::Type p_token_type);
template <class T> template <class T>
T *alloc_node(); T *alloc_node() {
T *node = memnew(T);
node->next = list;
list = node;
// TODO: Properly set positions for all nodes.
node->start_line = previous.start_line;
node->end_line = previous.end_line;
node->start_column = previous.start_column;
node->end_column = previous.end_column;
node->leftmost_column = previous.leftmost_column;
node->rightmost_column = previous.rightmost_column;
return node;
}
void clear(); void clear();
void push_error(const String &p_message, const Node *p_origin = nullptr); void push_error(const String &p_message, const Node *p_origin = nullptr);
#ifdef DEBUG_ENABLED
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols); void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
#endif
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false); void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
@ -1246,11 +1270,15 @@ public:
void get_annotation_list(List<MethodInfo> *r_annotations) const; void get_annotation_list(List<MethodInfo> *r_annotations) const;
const List<ParserError> &get_errors() const { return errors; } const List<ParserError> &get_errors() const { return errors; }
const List<GDScriptWarning> &get_warnings() const { return warnings; }
const List<String> get_dependencies() const { const List<String> get_dependencies() const {
// TODO: Keep track of deps. // TODO: Keep track of deps.
return List<String>(); return List<String>();
} }
#ifdef DEBUG_ENABLED
const List<GDScriptWarning> &get_warnings() const { return warnings; }
const Set<int> &get_unsafe_lines() const { return unsafe_lines; }
int get_last_line_number() const { return current.end_line; }
#endif
GDScriptParser(); GDScriptParser();
~GDScriptParser(); ~GDScriptParser();

View File

@ -111,6 +111,7 @@ static const char *token_names[] = {
"signal", // SIGNAL, "signal", // SIGNAL,
"static", // STATIC, "static", // STATIC,
"super", // SUPER, "super", // SUPER,
"trait", // TRAIT,
"var", // VAR, "var", // VAR,
"void", // VOID, "void", // VOID,
"yield", // YIELD, "yield", // YIELD,
@ -435,6 +436,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
KEYWORD("signal", Token::SIGNAL) \ KEYWORD("signal", Token::SIGNAL) \
KEYWORD("static", Token::STATIC) \ KEYWORD("static", Token::STATIC) \
KEYWORD("super", Token::SUPER) \ KEYWORD("super", Token::SUPER) \
KEYWORD_GROUP('t') \
KEYWORD("trait", Token::TRAIT) \
KEYWORD_GROUP('v') \ KEYWORD_GROUP('v') \
KEYWORD("var", Token::VAR) \ KEYWORD("var", Token::VAR) \
KEYWORD("void", Token::VOID) \ KEYWORD("void", Token::VOID) \

View File

@ -121,6 +121,7 @@ public:
SIGNAL, SIGNAL,
STATIC, STATIC,
SUPER, SUPER,
TRAIT,
VAR, VAR,
VOID, VOID,
YIELD, YIELD,

View File

@ -290,11 +290,11 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
symbol.script_path = path; symbol.script_path = path;
symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{"; symbol.detail = "enum " + String(m.m_enum->identifier->name) + "{";
for (int i = 0; i < m.m_enum->values.size(); i++) { for (int j = 0; j < m.m_enum->values.size(); j++) {
if (i > 0) { if (j > 0) {
symbol.detail += ", "; symbol.detail += ", ";
} }
symbol.detail += String(m.m_enum->values[i].identifier->name) + " = " + itos(m.m_enum->values[i].value); symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value);
} }
symbol.detail += "}"; symbol.detail += "}";
r_symbol.children.push_back(symbol); r_symbol.children.push_back(symbol);