Merge pull request #41381 from vnen/gdscript-2-fixes

A few more GDScript fixes
This commit is contained in:
Rémi Verschelde 2020-08-19 22:17:44 +02:00 committed by GitHub
commit eb9cbdc369
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 30 deletions

View File

@ -33,6 +33,7 @@
#include "core/class_db.h"
#include "core/hash_map.h"
#include "core/io/resource_loader.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#include "gdscript.h"
@ -1960,6 +1961,8 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
bool all_is_constant = true;
HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements;
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
if (p_dictionary->style == GDScriptParser::DictionaryNode::PYTHON_DICT) {
@ -1967,6 +1970,14 @@ void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dicti
}
reduce_expression(element.value);
all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant;
if (element.key->is_constant) {
if (elements.has(element.key->reduced_value)) {
push_error(vformat(R"(Key "%s" was already used in this dictionary (at line %d).)", element.key->reduced_value, elements[element.key->reduced_value]->start_line), element.key);
} else {
elements[element.key->reduced_value] = element.value;
}
}
}
if (all_is_constant) {
@ -2301,6 +2312,37 @@ void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) {
}
void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) {
if (!p_preload->path) {
return;
}
reduce_expression(p_preload->path);
if (!p_preload->path->is_constant) {
push_error("Preloaded path must be a constant string.", p_preload->path);
return;
}
if (p_preload->path->reduced_value.get_type() != Variant::STRING) {
push_error("Preloaded path must be a constant string.", p_preload->path);
} else {
p_preload->resolved_path = p_preload->path->reduced_value;
// TODO: Save this as script dependency.
if (p_preload->resolved_path.is_rel_path()) {
p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path);
}
p_preload->resolved_path = p_preload->resolved_path.simplify_path();
if (!FileAccess::exists(p_preload->resolved_path)) {
push_error(vformat(R"(Preload file "%s" does not exist.)", p_preload->resolved_path), p_preload->path);
} else {
// TODO: Don't load if validating: use completion cache.
p_preload->resource = ResourceLoader::load(p_preload->resolved_path);
if (p_preload->resource.is_null()) {
push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path);
}
}
}
p_preload->is_constant = true;
p_preload->reduced_value = p_preload->resource;
p_preload->set_datatype(type_from_variant(p_preload->reduced_value));
@ -2476,7 +2518,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
// Check resulting type if possible.
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::BUILTIN;
result_type.type_source = GDScriptParser::DataType::INFERRED;
result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
switch (base_type.builtin_type) {
// Can't index at all.

View File

@ -986,9 +986,15 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->identifier = parse_identifier();
if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
while (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
ParameterNode *parameter = parse_parameter();
if (parameter == nullptr) {
push_error("Expected signal parameter name.");
break;
}
if (parameter->default_value != nullptr) {
@ -1000,7 +1006,8 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() {
signal->parameters_indices[parameter->identifier->name] = signal->parameters.size();
signal->parameters.push_back(parameter);
}
}
} while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*");
}
@ -1022,6 +1029,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
HashMap<StringName, int> elements;
do {
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
break; // Allow trailing comma.
@ -1033,7 +1042,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
item.line = previous.start_line;
item.leftmost_column = previous.leftmost_column;
if (!named) {
if (elements.has(item.identifier->name)) {
push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier);
} else if (!named) {
// TODO: Abstract this recursive member check.
ClassNode *parent = current_class;
while (parent != nullptr) {
@ -1045,6 +1056,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
}
}
elements[item.identifier->name] = item.line;
if (match(GDScriptTokenizer::Token::EQUAL)) {
ExpressionNode *value = parse_expression(false);
if (value == nullptr) {
@ -1136,6 +1149,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() {
if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) {
make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function);
function->return_type = parse_type(true);
if (function->return_type == nullptr) {
push_error(R"(Expected return type or "void" after "->".)");
}
}
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
@ -2489,15 +2505,18 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
make_completion_context(COMPLETION_GET_NODE, get_node);
get_node->string = parse_literal();
return get_node;
} else if (check(GDScriptTokenizer::Token::IDENTIFIER)) {
} else if (current.is_node_name()) {
GetNodeNode *get_node = alloc_node<GetNodeNode>();
int chain_position = 0;
do {
make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expect node identifer after "/".)")) {
if (!current.is_node_name()) {
push_error(R"(Expect node path after "/".)");
return nullptr;
}
IdentifierNode *identifier = parse_identifier();
advance();
IdentifierNode *identifier = alloc_node<IdentifierNode>();
identifier->name = previous.get_identifier();
get_node->chain.push_back(identifier);
} while (match(GDScriptTokenizer::Token::SLASH));
return get_node;
@ -2521,29 +2540,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_
if (preload->path == nullptr) {
push_error(R"(Expected resource path after "(".)");
} else if (preload->path->type != Node::LITERAL) {
push_error("Preloaded path must be a constant string.");
} else {
LiteralNode *path = static_cast<LiteralNode *>(preload->path);
if (path->value.get_type() != Variant::STRING) {
push_error("Preloaded path must be a constant string.");
} else {
preload->resolved_path = path->value;
// TODO: Save this as script dependency.
if (preload->resolved_path.is_rel_path()) {
preload->resolved_path = script_path.get_base_dir().plus_file(preload->resolved_path);
}
preload->resolved_path = preload->resolved_path.simplify_path();
if (!FileAccess::exists(preload->resolved_path)) {
push_error(vformat(R"(Preload file "%s" does not exist.)", preload->resolved_path));
} else {
// TODO: Don't load if validating: use completion cache.
preload->resource = ResourceLoader::load(preload->resolved_path);
if (preload->resource.is_null()) {
push_error(vformat(R"(Could not preload resource file "%s".)", preload->resolved_path));
}
}
}
}
pop_completion_call();

View File

@ -168,6 +168,52 @@ bool GDScriptTokenizer::Token::is_identifier() const {
}
}
bool GDScriptTokenizer::Token::is_node_name() const {
// This is meant to allow keywords with the $ notation, but not as general identifiers.
switch (type) {
case IDENTIFIER:
case AND:
case AS:
case ASSERT:
case AWAIT:
case BREAK:
case BREAKPOINT:
case CLASS_NAME:
case CLASS:
case CONST:
case CONTINUE:
case ELIF:
case ELSE:
case ENUM:
case EXTENDS:
case FOR:
case FUNC:
case IF:
case IN:
case IS:
case MATCH:
case NAMESPACE:
case NOT:
case OR:
case PASS:
case PRELOAD:
case RETURN:
case SELF:
case SIGNAL:
case STATIC:
case SUPER:
case TRAIT:
case UNDERSCORE:
case VAR:
case VOID:
case WHILE:
case YIELD:
return true;
default:
return false;
}
}
String GDScriptTokenizer::get_token_name(Token::Type p_token_type) {
ERR_FAIL_INDEX_V_MSG(p_token_type, Token::TK_MAX, "<error>", "Using token type out of the enum.");
return token_names[p_token_type];

View File

@ -169,6 +169,7 @@ public:
const char *get_name() const;
bool is_identifier() const;
bool is_node_name() const;
StringName get_identifier() const { return source; }
Token(Type p_type) {