Merge pull request #41359 from vnen/gdscript-2-fixes
Assorted fixes for GDScript bugs
This commit is contained in:
commit
e2fb55471c
9 changed files with 187 additions and 43 deletions
|
@ -596,6 +596,21 @@ Error GDScript::reload(bool p_keep_state) {
|
|||
return OK;
|
||||
}
|
||||
|
||||
{
|
||||
String source_path = path;
|
||||
if (source_path.empty()) {
|
||||
source_path = get_path();
|
||||
}
|
||||
if (!source_path.empty()) {
|
||||
MutexLock lock(GDScriptCache::singleton->lock);
|
||||
Ref<GDScript> self(this);
|
||||
if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) {
|
||||
GDScriptCache::singleton->shallow_gdscript_cache[source_path] = self;
|
||||
self->unreference();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valid = false;
|
||||
GDScriptParser parser;
|
||||
Error err = parser.parse(source, path, false);
|
||||
|
|
|
@ -498,7 +498,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
|
||||
if (member.variable->initializer != nullptr) {
|
||||
if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
|
||||
} else {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(member.variable->initializer);
|
||||
}
|
||||
} 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);
|
||||
|
@ -596,17 +602,67 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
|
|||
enum_type.is_meta_type = true;
|
||||
enum_type.is_constant = true;
|
||||
|
||||
// Enums can't be nested, so we can safely override this.
|
||||
current_enum = member.m_enum;
|
||||
|
||||
for (int j = 0; j < member.m_enum->values.size(); j++) {
|
||||
enum_type.enum_values[member.m_enum->values[j].identifier->name] = member.m_enum->values[j].value;
|
||||
GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j];
|
||||
|
||||
if (element.custom_value) {
|
||||
reduce_expression(element.custom_value);
|
||||
if (!element.custom_value->is_constant) {
|
||||
push_error(R"(Enum values must be constant.)", element.custom_value);
|
||||
} else if (element.custom_value->reduced_value.get_type() != Variant::INT) {
|
||||
push_error(R"(Enum values must be integers.)", element.custom_value);
|
||||
} else {
|
||||
element.value = element.custom_value->reduced_value;
|
||||
element.resolved = true;
|
||||
}
|
||||
} else {
|
||||
if (element.index > 0) {
|
||||
element.value = element.parent_enum->values[element.index - 1].value + 1;
|
||||
} else {
|
||||
element.value = 0;
|
||||
}
|
||||
element.resolved = true;
|
||||
}
|
||||
|
||||
enum_type.enum_values[element.identifier->name] = element.value;
|
||||
}
|
||||
|
||||
current_enum = nullptr;
|
||||
|
||||
member.m_enum->set_datatype(enum_type);
|
||||
} break;
|
||||
case GDScriptParser::ClassNode::Member::FUNCTION:
|
||||
resolve_function_signature(member.function);
|
||||
break;
|
||||
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
|
||||
break; // Nothing to do, type and value set in parser.
|
||||
case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
|
||||
if (member.enum_value.custom_value) {
|
||||
current_enum = member.enum_value.parent_enum;
|
||||
reduce_expression(member.enum_value.custom_value);
|
||||
current_enum = nullptr;
|
||||
|
||||
if (!member.enum_value.custom_value->is_constant) {
|
||||
push_error(R"(Enum values must be constant.)", member.enum_value.custom_value);
|
||||
} else if (member.enum_value.custom_value->reduced_value.get_type() != Variant::INT) {
|
||||
push_error(R"(Enum values must be integers.)", member.enum_value.custom_value);
|
||||
} else {
|
||||
member.enum_value.value = member.enum_value.custom_value->reduced_value;
|
||||
member.enum_value.resolved = true;
|
||||
}
|
||||
} else {
|
||||
if (member.enum_value.index > 0) {
|
||||
member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1;
|
||||
} else {
|
||||
member.enum_value.value = 0;
|
||||
}
|
||||
member.enum_value.resolved = true;
|
||||
}
|
||||
// Also update the original references.
|
||||
member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value;
|
||||
p_class->members.write[i].enum_value = member.enum_value;
|
||||
} break;
|
||||
case GDScriptParser::ClassNode::Member::CLASS:
|
||||
break; // Done later.
|
||||
case GDScriptParser::ClassNode::Member::UNDEFINED:
|
||||
|
@ -994,7 +1050,13 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
|
|||
|
||||
if (p_variable->initializer != nullptr) {
|
||||
if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
|
||||
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
|
||||
} else {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(p_variable->initializer);
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
} else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) {
|
||||
parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION);
|
||||
|
@ -1385,9 +1447,16 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
|
|||
compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true);
|
||||
if (!compatible) {
|
||||
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);
|
||||
// Try reverse test since it can be a masked subtype.
|
||||
if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) {
|
||||
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 {
|
||||
// TODO: Add warning.
|
||||
mark_node_unsafe(p_assignment);
|
||||
}
|
||||
} else {
|
||||
// TODO: Warning in this case.
|
||||
mark_node_unsafe(p_assignment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1634,6 +1703,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
|
|||
}
|
||||
signature += p_call->arguments[i]->get_datatype().to_string();
|
||||
}
|
||||
signature += ")";
|
||||
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee);
|
||||
} break;
|
||||
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
|
||||
|
@ -1684,7 +1754,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
|
|||
for (int i = 0; i < p_call->arguments.size(); i++) {
|
||||
GDScriptParser::DataType par_type = type_from_property(info.arguments[i]);
|
||||
|
||||
if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype())) {
|
||||
if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) {
|
||||
types_match = false;
|
||||
break;
|
||||
#ifdef DEBUG_ENABLED
|
||||
|
@ -1711,6 +1781,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa
|
|||
}
|
||||
signature += p_call->arguments[i]->get_datatype().to_string();
|
||||
}
|
||||
signature += ")";
|
||||
push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call);
|
||||
}
|
||||
}
|
||||
|
@ -2104,6 +2175,33 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
|
|||
|
||||
void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) {
|
||||
// TODO: This is opportunity to further infer types.
|
||||
|
||||
// Check if we are inside and enum. This allows enum values to access other elements of the same enum.
|
||||
if (current_enum) {
|
||||
for (int i = 0; i < current_enum->values.size(); i++) {
|
||||
const GDScriptParser::EnumNode::Value &element = current_enum->values[i];
|
||||
if (element.identifier->name == p_identifier->name) {
|
||||
GDScriptParser::DataType type;
|
||||
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
|
||||
type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
|
||||
type.builtin_type = Variant::INT;
|
||||
type.is_constant = true;
|
||||
if (element.parent_enum->identifier) {
|
||||
type.enum_type = element.parent_enum->identifier->name;
|
||||
}
|
||||
p_identifier->set_datatype(type);
|
||||
|
||||
if (element.resolved) {
|
||||
p_identifier->is_constant = true;
|
||||
p_identifier->reduced_value = element.value;
|
||||
} else {
|
||||
push_error(R"(Cannot use another enum element before it was declared.)", p_identifier);
|
||||
}
|
||||
return; // Found anyway.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if identifier is local.
|
||||
// If that's the case, the declaration already was solved before.
|
||||
switch (p_identifier->source) {
|
||||
|
@ -2509,6 +2607,12 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op)
|
|||
|
||||
GDScriptParser::DataType result;
|
||||
|
||||
if (p_unary_op->operand == nullptr) {
|
||||
result.kind = GDScriptParser::DataType::VARIANT;
|
||||
p_unary_op->set_datatype(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_unary_op->operand->is_constant) {
|
||||
p_unary_op->is_constant = true;
|
||||
p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant());
|
||||
|
@ -2883,7 +2987,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
|
|||
}
|
||||
|
||||
// Avoid error in formatting operator (%) where it doesn't find a placeholder.
|
||||
if (a_type == Variant::STRING) {
|
||||
if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
|
||||
a = String("%s");
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ class GDScriptAnalyzer {
|
|||
GDScriptParser *parser = nullptr;
|
||||
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
|
||||
|
||||
const GDScriptParser::EnumNode *current_enum = nullptr;
|
||||
|
||||
Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
|
||||
GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
|
||||
|
||||
|
|
|
@ -85,6 +85,17 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
if (result != OK) {
|
||||
if (parser != nullptr) {
|
||||
memdelete(parser);
|
||||
parser = nullptr;
|
||||
}
|
||||
if (analyzer != nullptr) {
|
||||
memdelete(analyzer);
|
||||
analyzer = nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -118,6 +129,10 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP
|
|||
if (singleton->parser_map.has(p_path)) {
|
||||
ref = singleton->parser_map[p_path];
|
||||
} else {
|
||||
if (!FileAccess::exists(p_path)) {
|
||||
r_error = ERR_FILE_NOT_FOUND;
|
||||
return ref;
|
||||
}
|
||||
GDScriptParser *parser = memnew(GDScriptParser);
|
||||
ref.instance();
|
||||
ref->parser = parser;
|
||||
|
|
|
@ -466,44 +466,40 @@ void GDScriptParser::pop_multiline() {
|
|||
}
|
||||
|
||||
bool GDScriptParser::is_statement_end() {
|
||||
return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON);
|
||||
return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF);
|
||||
}
|
||||
|
||||
void GDScriptParser::end_statement(const String &p_context) {
|
||||
bool found = false;
|
||||
while (is_statement_end()) {
|
||||
while (is_statement_end() && !is_at_end()) {
|
||||
// Remove sequential newlines/semicolons.
|
||||
found = true;
|
||||
advance();
|
||||
}
|
||||
if (!found) {
|
||||
if (!found && !is_at_end()) {
|
||||
push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name()));
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptParser::parse_program() {
|
||||
if (current.type == GDScriptTokenizer::Token::TK_EOF) {
|
||||
// Empty file.
|
||||
push_error("Source file is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
head = alloc_node<ClassNode>();
|
||||
current_class = head;
|
||||
|
||||
if (match(GDScriptTokenizer::Token::ANNOTATION)) {
|
||||
// Check for @tool annotation.
|
||||
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::CLASS_LEVEL);
|
||||
if (annotation->name == "@tool") {
|
||||
// TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
|
||||
_is_tool = true;
|
||||
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
||||
push_error(R"(Expected newline after "@tool" annotation.)");
|
||||
if (annotation != nullptr) {
|
||||
if (annotation->name == "@tool") {
|
||||
// TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?).
|
||||
_is_tool = true;
|
||||
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
|
||||
push_error(R"(Expected newline after "@tool" annotation.)");
|
||||
}
|
||||
// @tool annotation has no specific target.
|
||||
annotation->apply(this, nullptr);
|
||||
} else {
|
||||
annotation_stack.push_back(annotation);
|
||||
}
|
||||
// @tool annotation has no specific target.
|
||||
annotation->apply(this, nullptr);
|
||||
} else {
|
||||
annotation_stack.push_back(annotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1026,8 +1022,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|||
push_multiline(true);
|
||||
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
|
||||
|
||||
int current_value = 0;
|
||||
|
||||
do {
|
||||
if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) {
|
||||
break; // Allow trailing comma.
|
||||
|
@ -1035,6 +1029,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifer for enum key.)")) {
|
||||
EnumNode::Value item;
|
||||
item.identifier = parse_identifier();
|
||||
item.parent_enum = enum_node;
|
||||
item.line = previous.start_line;
|
||||
item.leftmost_column = previous.leftmost_column;
|
||||
|
||||
if (!named) {
|
||||
// TODO: Abstract this recursive member check.
|
||||
|
@ -1049,18 +1046,15 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() {
|
|||
}
|
||||
|
||||
if (match(GDScriptTokenizer::Token::EQUAL)) {
|
||||
if (consume(GDScriptTokenizer::Token::LITERAL, R"(Expected integer value after "=".)")) {
|
||||
item.custom_value = parse_literal();
|
||||
|
||||
if (item.custom_value->value.get_type() != Variant::INT) {
|
||||
push_error(R"(Expected integer value after "=".)");
|
||||
item.custom_value = nullptr;
|
||||
} else {
|
||||
current_value = item.custom_value->value;
|
||||
}
|
||||
ExpressionNode *value = parse_expression(false);
|
||||
if (value == nullptr) {
|
||||
push_error(R"(Expected expression value after "=".)");
|
||||
}
|
||||
item.custom_value = value;
|
||||
}
|
||||
item.value = current_value++;
|
||||
item.rightmost_column = previous.rightmost_column;
|
||||
|
||||
item.index = enum_node->values.size();
|
||||
enum_node->values.push_back(item);
|
||||
if (!named) {
|
||||
// Add as member of current class.
|
||||
|
|
|
@ -405,7 +405,10 @@ public:
|
|||
struct EnumNode : public Node {
|
||||
struct Value {
|
||||
IdentifierNode *identifier = nullptr;
|
||||
LiteralNode *custom_value = nullptr;
|
||||
ExpressionNode *custom_value = nullptr;
|
||||
EnumNode *parent_enum = nullptr;
|
||||
int index = -1;
|
||||
bool resolved = false;
|
||||
int value = 0;
|
||||
int line = 0;
|
||||
int leftmost_column = 0;
|
||||
|
|
|
@ -156,6 +156,18 @@ const char *GDScriptTokenizer::Token::get_name() const {
|
|||
return token_names[type];
|
||||
}
|
||||
|
||||
bool GDScriptTokenizer::Token::is_identifier() const {
|
||||
// Note: Most keywords should not be recognized as identifiers.
|
||||
// These are only exceptions for stuff that already is on the engine's API.
|
||||
switch (type) {
|
||||
case IDENTIFIER:
|
||||
case MATCH: // Used in String.match().
|
||||
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];
|
||||
|
|
|
@ -168,9 +168,8 @@ public:
|
|||
String source;
|
||||
|
||||
const char *get_name() const;
|
||||
// TODO: Allow some keywords as identifiers?
|
||||
bool is_identifier() const { return type == IDENTIFIER; }
|
||||
StringName get_identifier() const { return literal; }
|
||||
bool is_identifier() const;
|
||||
StringName get_identifier() const { return source; }
|
||||
|
||||
Token(Type p_type) {
|
||||
type = p_type;
|
||||
|
|
|
@ -237,7 +237,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p
|
|||
case ClassNode::Member::ENUM_VALUE: {
|
||||
lsp::DocumentSymbol symbol;
|
||||
|
||||
symbol.name = m.constant->identifier->name;
|
||||
symbol.name = m.enum_value.identifier->name;
|
||||
symbol.kind = lsp::SymbolKind::EnumMember;
|
||||
symbol.deprecated = false;
|
||||
symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line);
|
||||
|
|
Loading…
Reference in a new issue