summaryrefslogtreecommitdiff
path: root/modules/gdscript/gdscript_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r--modules/gdscript/gdscript_parser.cpp3593
1 files changed, 3390 insertions, 203 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index fdb92a68a9..852d465206 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -30,6 +30,10 @@
#include "gdscript_parser.h"
+#include "core/core_string_names.h"
+#include "core/engine.h"
+#include "core/project_settings.h"
+#include "core/reference.h"
#include "gdscript.h"
#include "io/resource_loader.h"
#include "os/file_access.h"
@@ -138,8 +142,9 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo
}
Node *arg = _parse_expression(p_parent, p_static);
- if (!arg)
+ if (!arg) {
return false;
+ }
p_args.push_back(arg);
@@ -263,6 +268,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
bool need_identifier = true;
bool done = false;
+ int line = tokenizer->get_token_line();
while (!done) {
@@ -330,16 +336,19 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_CALL;
-
+ op->line = line;
op->arguments.push_back(alloc_node<SelfNode>());
+ op->arguments[0]->line = line;
IdentifierNode *funcname = alloc_node<IdentifierNode>();
funcname->name = "get_node";
-
+ funcname->line = line;
op->arguments.push_back(funcname);
ConstantNode *nodepath = alloc_node<ConstantNode>();
nodepath->value = NodePath(StringName(path));
+ nodepath->datatype = _type_from_variant(nodepath->value);
+ nodepath->line = line;
op->arguments.push_back(nodepath);
expr = op;
@@ -353,6 +362,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = tokenizer->get_token_constant();
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_PI) {
@@ -360,6 +370,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_PI;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_TAU) {
@@ -367,6 +378,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_TAU;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_INF) {
@@ -374,6 +386,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_INF;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONST_NAN) {
@@ -381,6 +394,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//constant defined by tokenizer
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = Math_NAN;
+ constant->datatype = _type_from_variant(constant->value);
tokenizer->advance();
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_PRELOAD) {
@@ -419,15 +433,13 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
if (subexpr->type == Node::TYPE_IDENTIFIER) {
IdentifierNode *in = static_cast<IdentifierNode *>(subexpr);
- Vector<ClassNode::Constant> ce = current_class->constant_expressions;
// Try to find the constant expression by the identifier
- for (int i = 0; i < ce.size(); ++i) {
- if (ce[i].identifier == in->name) {
- if (ce[i].expression->type == Node::TYPE_CONSTANT) {
- cn = static_cast<ConstantNode *>(ce[i].expression);
- found_constant = true;
- }
+ if (current_class->constant_expressions.has(in->name)) {
+ Node *cn_exp = current_class->constant_expressions[in->name].expression;
+ if (cn_exp->type == Node::TYPE_CONSTANT) {
+ cn = static_cast<ConstantNode *>(cn_exp);
+ found_constant = true;
}
}
}
@@ -480,15 +492,28 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
_set_error("Expected ')' after 'preload' path");
return NULL;
}
+
+ Ref<GDScript> gds = res;
+ if (gds.is_valid() && !gds->is_valid()) {
+ _set_error("Could not fully preload the script, possible cyclic reference or compilation error.");
+ return NULL;
+ }
+
tokenizer->advance();
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = res;
+ constant->datatype = _type_from_variant(constant->value);
expr = constant;
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) {
- //constant defined by tokenizer
+ if (!current_function) {
+ _set_error("yield() can only be used inside function blocks.");
+ return NULL;
+ }
+
+ current_function->has_yield = true;
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
@@ -618,6 +643,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = Variant::get_numeric_constant_value(bi_type, identifier);
+ cn->datatype = _type_from_variant(cn->value);
expr = cn;
}
@@ -695,24 +721,56 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
const ClassNode *cln = current_class;
bool bfn = false;
StringName identifier;
+ int id_line = tokenizer->get_token_line();
if (_get_completable_identifier(COMPLETION_IDENTIFIER, identifier)) {
}
- if (p_parsing_constant) {
- for (int i = 0; i < cln->constant_expressions.size(); ++i) {
-
- if (cln->constant_expressions[i].identifier == identifier) {
+ BlockNode *b = current_block;
+ while (b) {
+ if (b->variables.has(identifier)) {
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ LocalVarNode *lv = b->variables[identifier];
+ id->name = identifier;
+ id->declared_block = b;
+ id->line = id_line;
+ expr = id;
+ bfn = true;
- expr = cln->constant_expressions[i].expression;
- bfn = true;
- break;
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_OP_ASSIGN_ADD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_AND:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_OR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_BIT_XOR:
+ case GDScriptTokenizer::TK_OP_ASSIGN_DIV:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MOD:
+ case GDScriptTokenizer::TK_OP_ASSIGN_MUL:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_LEFT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SHIFT_RIGHT:
+ case GDScriptTokenizer::TK_OP_ASSIGN_SUB: {
+ if (lv->assignments == 0 && !lv->datatype.has_type) {
+ _set_error("Using assignment with operation on a variable that was never assigned.");
+ return NULL;
+ }
+ } // fallthrough
+ case GDScriptTokenizer::TK_OP_ASSIGN: {
+ lv->assignments += 1;
+ }
}
+ break;
}
+ b = b->parent_block;
+ }
- if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
+ if (!bfn && p_parsing_constant) {
+ if (cln->constant_expressions.has(identifier)) {
+ expr = cln->constant_expressions[identifier].expression;
+ bfn = true;
+ } else if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) {
//check from constants
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = GDScriptLanguage::get_singleton()->get_global_array()[GDScriptLanguage::get_singleton()->get_global_map()[identifier]];
+ constant->datatype = _type_from_variant(constant->value);
+ constant->line = id_line;
expr = constant;
bfn = true;
}
@@ -729,6 +787,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (!bfn) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = identifier;
+ id->line = id_line;
expr = id;
}
@@ -902,6 +961,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//lua style identifier, easier to write
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = tokenizer->get_token_literal();
+ cn->datatype = _type_from_variant(cn->value);
key = cn;
tokenizer->advance(2);
expecting = DICT_EXPECT_VALUE;
@@ -941,7 +1001,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
expr = dict;
- } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR) && tokenizer->get_token(2) == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ } else if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD && (tokenizer->is_token_literal(1) || tokenizer->get_token(1) == GDScriptTokenizer::TK_CURSOR)) {
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
// parent call
@@ -953,17 +1013,23 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(self);
forbidden for now */
StringName identifier;
- if (_get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier)) {
- //indexing stuff
- }
+ bool is_completion = _get_completable_identifier(COMPLETION_PARENT_FUNCTION, identifier) && for_completion;
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = identifier;
op->arguments.push_back(id);
- tokenizer->advance(1);
- if (!_parse_arguments(op, op->arguments, p_static))
- return NULL;
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ if (!is_completion) {
+ _set_error("Expected '(' for parent function call.");
+ return NULL;
+ }
+ } else {
+ tokenizer->advance();
+ if (!_parse_arguments(op, op->arguments, p_static)) {
+ return NULL;
+ }
+ }
expr = op;
@@ -1087,6 +1153,26 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
break;
}
+ /*****************/
+ /* Parse Casting */
+ /*****************/
+
+ bool has_casting = expr->type == Node::TYPE_CAST;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_AS) {
+ if (has_casting) {
+ _set_error("Unexpected 'as'.");
+ return NULL;
+ }
+ CastNode *cn = alloc_node<CastNode>();
+ if (!_parse_type(cn->cast_type)) {
+ _set_error("Expected type after 'as'.");
+ return NULL;
+ }
+ has_casting = true;
+ cn->source_node = expr;
+ expr = cn;
+ }
+
/******************/
/* Parse Operator */
/******************/
@@ -1110,7 +1196,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
//assign, if allowed is only allowed on the first operator
#define _VALIDATE_ASSIGN \
- if (!p_allow_assign) { \
+ if (!p_allow_assign || has_casting) { \
_set_error("Unexpected assign."); \
return NULL; \
} \
@@ -1325,8 +1411,8 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->op = expression[i].op;
op->arguments.push_back(expression[i + 1].node);
op->line = op_line; //line might have been changed from a \n
- expression[i].is_op = false;
- expression[i].node = op;
+ expression.write[i].is_op = false;
+ expression.write[i].node = op;
expression.remove(i + 1);
}
@@ -1338,11 +1424,11 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (next_op >= (expression.size() - 2) || expression[next_op + 2].op != OperatorNode::OP_TERNARY_ELSE) {
_set_error("Expected else after ternary if.");
- ERR_FAIL_V(NULL);
+ return NULL;
}
if (next_op >= (expression.size() - 3)) {
_set_error("Expected value after ternary else.");
- ERR_FAIL_V(NULL);
+ return NULL;
}
OperatorNode *op = alloc_node<OperatorNode>();
@@ -1380,7 +1466,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(expression[next_op + 3].node); //expression after next goes as when-false
//replace all 3 nodes by this operator and make it an expression
- expression[next_op - 1].node = op;
+ expression.write[next_op - 1].node = op;
expression.remove(next_op);
expression.remove(next_op);
expression.remove(next_op);
@@ -1416,7 +1502,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
op->arguments.push_back(expression[next_op + 1].node); //next expression goes as right
//replace all 3 nodes by this operator and make it an expression
- expression[next_op - 1].node = op;
+ expression.write[next_op - 1].node = op;
expression.remove(next_op);
expression.remove(next_op);
}
@@ -1440,7 +1526,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < an->elements.size(); i++) {
- an->elements[i] = _reduce_expression(an->elements[i], p_to_const);
+ an->elements.write[i] = _reduce_expression(an->elements[i], p_to_const);
if (an->elements[i]->type != Node::TYPE_CONSTANT)
all_constants = false;
}
@@ -1450,13 +1536,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
Array arr;
- //print_line("mk array "+itos(!p_to_const));
arr.resize(an->elements.size());
for (int i = 0; i < an->elements.size(); i++) {
ConstantNode *acn = static_cast<ConstantNode *>(an->elements[i]);
arr[i] = acn->value;
}
cn->value = arr;
+ cn->datatype = _type_from_variant(cn->value);
return cn;
}
@@ -1470,10 +1556,10 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < dn->elements.size(); i++) {
- dn->elements[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
+ dn->elements.write[i].key = _reduce_expression(dn->elements[i].key, p_to_const);
if (dn->elements[i].key->type != Node::TYPE_CONSTANT)
all_constants = false;
- dn->elements[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
+ dn->elements.write[i].value = _reduce_expression(dn->elements[i].value, p_to_const);
if (dn->elements[i].value->type != Node::TYPE_CONSTANT)
all_constants = false;
}
@@ -1490,6 +1576,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
dict[key_c->value] = value_c->value;
}
cn->value = dict;
+ cn->datatype = _type_from_variant(cn->value);
return cn;
}
@@ -1505,7 +1592,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < op->arguments.size(); i++) {
- op->arguments[i] = _reduce_expression(op->arguments[i], p_to_const);
+ op->arguments.write[i] = _reduce_expression(op->arguments[i], p_to_const);
if (op->arguments[i]->type != Node::TYPE_CONSTANT) {
all_constants = false;
last_not_constant = i;
@@ -1533,7 +1620,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
for (int i = 0; i < ptrs.size(); i++) {
ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[i + 1]);
- ptrs[i] = &cn->value;
+ ptrs.write[i] = &cn->value;
}
vptr = (const Variant **)&ptrs[0];
@@ -1591,6 +1678,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
} else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) {
@@ -1620,24 +1708,9 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
-
- } /*else if (op->arguments[0]->type==Node::TYPE_CONSTANT && op->arguments[1]->type==Node::TYPE_IDENTIFIER) {
-
- ConstantNode *ca = static_cast<ConstantNode*>(op->arguments[0]);
- IdentifierNode *ib = static_cast<IdentifierNode*>(op->arguments[1]);
-
- bool valid;
- Variant v = ca->value.get_named(ib->name,&valid);
- if (!valid) {
- _set_error("invalid index '"+String(ib->name)+"' in constant expression");
- return op;
- }
-
- ConstantNode *cn = alloc_node<ConstantNode>();
- cn->value=v;
- return cn;
- }*/
+ }
return op;
@@ -1658,13 +1731,14 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = v;
+ cn->datatype = _type_from_variant(v);
return cn;
}
return op;
}
- //validate assignment (don't assign to cosntant expression
+ //validate assignment (don't assign to constant expression
switch (op->op) {
case OperatorNode::OP_ASSIGN:
@@ -1711,6 +1785,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
} \
ConstantNode *cn = alloc_node<ConstantNode>(); \
cn->value = res; \
+ cn->datatype = _type_from_variant(res); \
return cn;
#define _REDUCE_BINARY(m_vop) \
@@ -1724,6 +1799,7 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
} \
ConstantNode *cn = alloc_node<ConstantNode>(); \
cn->value = res; \
+ cn->datatype = _type_from_variant(res); \
return cn;
switch (op->op) {
@@ -1799,6 +1875,13 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
case OperatorNode::OP_BIT_XOR: {
_REDUCE_BINARY(Variant::OP_BIT_XOR);
} break;
+ case OperatorNode::OP_TERNARY_IF: {
+ if (static_cast<ConstantNode *>(op->arguments[0])->value.booleanize()) {
+ return op->arguments[1];
+ } else {
+ return op->arguments[2];
+ }
+ } break;
default: { ERR_FAIL_V(op); }
}
@@ -1905,6 +1988,15 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
tokenizer->advance();
pattern->pt_type = GDScriptParser::PatternNode::PT_BIND;
pattern->bind = tokenizer->get_token_identifier();
+ // Check if binding is already used
+ if (current_block->variables.has(pattern->bind)) {
+ _set_error("Binding name of '" + pattern->bind.operator String() + "' was already used in the pattern.");
+ return NULL;
+ }
+ // Create local variable for proper identifier detection later
+ LocalVarNode *lv = alloc_node<LocalVarNode>();
+ lv->name = pattern->bind;
+ current_block->variables.insert(lv->name, lv);
tokenizer->advance();
} break;
// dictionary
@@ -2038,18 +2130,34 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
}
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
+ branch->body = alloc_node<BlockNode>();
+ branch->body->parent_block = p_block;
+ p_block->sub_blocks.push_back(branch->body);
+ current_block = branch->body;
branch->patterns.push_back(_parse_pattern(p_static));
if (!branch->patterns[0]) {
return;
}
+ bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
+ bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
+
while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
branch->patterns.push_back(_parse_pattern(p_static));
if (!branch->patterns[branch->patterns.size() - 1]) {
return;
}
+
+ PatternNode::PatternType pt = branch->patterns[branch->patterns.size() - 1]->pt_type;
+
+ if (pt == PatternNode::PT_BIND) {
+ _set_error("Cannot use bindings with multipattern.");
+ return;
+ }
+
+ catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
}
if (!_enter_indent_block()) {
@@ -2057,54 +2165,76 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
return;
}
- branch->body = alloc_node<BlockNode>();
- branch->body->parent_block = p_block;
- p_block->sub_blocks.push_back(branch->body);
- current_block = branch->body;
-
_parse_block(branch->body, p_static);
current_block = p_block;
+ if (catch_all && branch->body->has_return) {
+ p_block->has_return = true;
+ }
+
p_branches.push_back(branch);
}
}
void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
+
+ const DataType &to_match_type = p_node_to_match->get_datatype();
+
switch (p_pattern->pt_type) {
case PatternNode::PT_CONSTANT: {
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
+ DataType pattern_type = _reduce_node_type(p_pattern->constant);
+ if (error_set) {
+ return;
+ }
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
+ OperatorNode *type_comp = NULL;
- OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
- typeof_pattern_value->op = OperatorNode::OP_CALL;
- typeof_pattern_value->arguments.push_back(typeof_node);
- typeof_pattern_value->arguments.push_back(p_pattern->constant);
+ // static type check if possible
+ if (pattern_type.has_type && to_match_type.has_type) {
+ if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
+ _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").",
+ p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
+
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_pattern_value);
+ OperatorNode *typeof_pattern_value = alloc_node<OperatorNode>();
+ typeof_pattern_value->op = OperatorNode::OP_CALL;
+ typeof_pattern_value->arguments.push_back(typeof_node);
+ typeof_pattern_value->arguments.push_back(p_pattern->constant);
- // comare the actual values
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_pattern_value);
+ }
+
+ // compare the actual values
OperatorNode *value_comp = alloc_node<OperatorNode>();
value_comp->op = OperatorNode::OP_EQUAL;
value_comp->arguments.push_back(p_pattern->constant);
value_comp->arguments.push_back(p_node_to_match);
- OperatorNode *comparison = alloc_node<OperatorNode>();
- comparison->op = OperatorNode::OP_AND;
- comparison->arguments.push_back(type_comp);
- comparison->arguments.push_back(value_comp);
+ if (type_comp) {
+ OperatorNode *full_comparison = alloc_node<OperatorNode>();
+ full_comparison->op = OperatorNode::OP_AND;
+ full_comparison->arguments.push_back(type_comp);
+ full_comparison->arguments.push_back(value_comp);
- p_resulting_node = comparison;
+ p_resulting_node = full_comparison;
+ } else {
+ p_resulting_node = value_comp;
+ }
} break;
case PatternNode::PT_BIND: {
@@ -2129,22 +2259,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
// typeof(value_to_match) == TYPE_ARRAY && value_to_match.size() == length
{
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
- typeof_array->name = "TYPE_ARRAY";
-
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_array);
+ OperatorNode *type_comp = NULL;
+ // static type check if possible
+ if (to_match_type.has_type) {
+ // must be an array
+ if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::ARRAY) {
+ _set_error("Cannot match an array pattern with a non-array expression.", p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
+
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
+
+ IdentifierNode *typeof_array = alloc_node<IdentifierNode>();
+ typeof_array->name = "TYPE_ARRAY";
+
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_array);
+ }
// size
ConstantNode *length = alloc_node<ConstantNode>();
@@ -2163,12 +2303,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
length_comparison->arguments.push_back(call);
length_comparison->arguments.push_back(length);
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
+ if (type_comp) {
+ OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
+ type_and_length_comparison->op = OperatorNode::OP_AND;
+ type_and_length_comparison->arguments.push_back(type_comp);
+ type_and_length_comparison->arguments.push_back(length_comparison);
- p_resulting_node = type_and_length_comparison;
+ p_resulting_node = type_and_length_comparison;
+ } else {
+ p_resulting_node = length_comparison;
+ }
}
for (int i = 0; i < p_pattern->array.size(); i++) {
@@ -2208,22 +2352,32 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
// typeof(value_to_match) == TYPE_DICTIONARY && value_to_match.size() == length
{
- // typecheck
- BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
- typeof_node->function = GDScriptFunctions::TYPE_OF;
-
- OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
- typeof_match_value->op = OperatorNode::OP_CALL;
- typeof_match_value->arguments.push_back(typeof_node);
- typeof_match_value->arguments.push_back(p_node_to_match);
-
- IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
- typeof_dictionary->name = "TYPE_DICTIONARY";
-
- OperatorNode *type_comp = alloc_node<OperatorNode>();
- type_comp->op = OperatorNode::OP_EQUAL;
- type_comp->arguments.push_back(typeof_match_value);
- type_comp->arguments.push_back(typeof_dictionary);
+ OperatorNode *type_comp = NULL;
+ // static type check if possible
+ if (to_match_type.has_type) {
+ // must be an dictionary
+ if (to_match_type.kind != DataType::BUILTIN || to_match_type.builtin_type != Variant::DICTIONARY) {
+ _set_error("Cannot match an dictionary pattern with a non-dictionary expression.", p_pattern->line);
+ return;
+ }
+ } else {
+ // runtime typecheck
+ BuiltInFunctionNode *typeof_node = alloc_node<BuiltInFunctionNode>();
+ typeof_node->function = GDScriptFunctions::TYPE_OF;
+
+ OperatorNode *typeof_match_value = alloc_node<OperatorNode>();
+ typeof_match_value->op = OperatorNode::OP_CALL;
+ typeof_match_value->arguments.push_back(typeof_node);
+ typeof_match_value->arguments.push_back(p_node_to_match);
+
+ IdentifierNode *typeof_dictionary = alloc_node<IdentifierNode>();
+ typeof_dictionary->name = "TYPE_DICTIONARY";
+
+ type_comp = alloc_node<OperatorNode>();
+ type_comp->op = OperatorNode::OP_EQUAL;
+ type_comp->arguments.push_back(typeof_match_value);
+ type_comp->arguments.push_back(typeof_dictionary);
+ }
// size
ConstantNode *length = alloc_node<ConstantNode>();
@@ -2242,12 +2396,16 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
length_comparison->arguments.push_back(call);
length_comparison->arguments.push_back(length);
- OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
- type_and_length_comparison->op = OperatorNode::OP_AND;
- type_and_length_comparison->arguments.push_back(type_comp);
- type_and_length_comparison->arguments.push_back(length_comparison);
+ if (type_comp) {
+ OperatorNode *type_and_length_comparison = alloc_node<OperatorNode>();
+ type_and_length_comparison->op = OperatorNode::OP_AND;
+ type_and_length_comparison->arguments.push_back(type_comp);
+ type_and_length_comparison->arguments.push_back(length_comparison);
- p_resulting_node = type_and_length_comparison;
+ p_resulting_node = type_and_length_comparison;
+ } else {
+ p_resulting_node = length_comparison;
+ }
}
for (Map<ConstantNode *, PatternNode *>::Element *e = p_pattern->dictionary.front(); e; e = e->next()) {
@@ -2308,9 +2466,20 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
}
}
-void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_match_statement) {
+void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = "#match_value";
+ id->line = p_match_statement->line;
+ id->datatype = _reduce_node_type(p_match_statement->val_to_match);
+ if (id->datatype.has_type) {
+ _mark_line_as_safe(id->line);
+ } else {
+ _mark_line_as_unsafe(id->line);
+ }
+
+ if (error_set) {
+ return;
+ }
for (int i = 0; i < p_match_statement->branches.size(); i++) {
@@ -2323,11 +2492,16 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
for (int j = 0; j < branch->patterns.size(); j++) {
PatternNode *pattern = branch->patterns[j];
+ _mark_line_as_safe(pattern->line);
Map<StringName, Node *> bindings;
- Node *resulting_node;
+ Node *resulting_node = NULL;
_generate_pattern(pattern, id, resulting_node, bindings);
+ if (!resulting_node) {
+ return;
+ }
+
if (!binding.empty() && !bindings.empty()) {
_set_error("Multipatterns can't contain bindings");
return;
@@ -2335,6 +2509,14 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
binding = bindings;
}
+ // Result is always a boolean
+ DataType resulting_node_type;
+ resulting_node_type.has_type = true;
+ resulting_node_type.is_constant = true;
+ resulting_node_type.kind = DataType::BUILTIN;
+ resulting_node_type.builtin_type = Variant::BOOL;
+ resulting_node->set_datatype(resulting_node_type);
+
if (compiled_branch.compiled_pattern) {
OperatorNode *or_node = alloc_node<OperatorNode>();
or_node->op = OperatorNode::OP_OR;
@@ -2350,12 +2532,19 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_
// prepare the body ...hehe
for (Map<StringName, Node *>::Element *e = binding.front(); e; e = e->next()) {
- LocalVarNode *local_var = alloc_node<LocalVarNode>();
- local_var->name = e->key();
+ if (!branch->body->variables.has(e->key())) {
+ _set_error("Parser bug: missing pattern bind variable.", branch->line);
+ ERR_FAIL();
+ }
+
+ LocalVarNode *local_var = branch->body->variables[e->key()];
local_var->assign = e->value();
+ local_var->set_datatype(local_var->assign->get_datatype());
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = local_var->name;
+ id->declared_block = branch->body;
+ id->set_datatype(local_var->assign->get_datatype());
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_ASSIGN;
@@ -2413,7 +2602,21 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
switch (token) {
-
+ case GDScriptTokenizer::TK_EOF:
+ case GDScriptTokenizer::TK_ERROR:
+ case GDScriptTokenizer::TK_NEWLINE:
+ case GDScriptTokenizer::TK_CF_PASS: {
+ // will check later
+ } break;
+ default: {
+ // TODO: Make this a warning
+ /*if (p_block->has_return) {
+ _set_error("Unreacheable code.");
+ return;
+ }*/
+ } break;
+ }
+ switch (token) {
case GDScriptTokenizer::TK_EOF:
p_block->end_line = tokenizer->get_token_line();
case GDScriptTokenizer::TK_ERROR: {
@@ -2443,6 +2646,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_set_error("Expected ';' or <NewLine>.");
return;
}
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
// Ignore semicolon after 'pass'
@@ -2453,6 +2657,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
//variale declaration and (eventual) initialization
tokenizer->advance();
+ int var_line = tokenizer->get_token_line();
if (!tokenizer->is_token_literal(0, true)) {
_set_error("Expected identifier for local variable name.");
@@ -2470,24 +2675,34 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
BlockNode *check_block = p_block;
while (check_block) {
- for (int i = 0; i < check_block->variables.size(); i++) {
- if (n == check_block->variables[i]) {
- _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variable_lines[i]) + ").");
- return;
- }
+ if (check_block->variables.has(n)) {
+ _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ").");
+ return;
}
check_block = check_block->parent_block;
}
- int var_line = tokenizer->get_token_line();
-
//must know when the local variable is declared
LocalVarNode *lv = alloc_node<LocalVarNode>();
lv->name = n;
+ lv->line = var_line;
p_block->statements.push_back(lv);
Node *assigned = NULL;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ lv->datatype = DataType();
+#ifdef DEBUG_ENABLED
+ lv->datatype.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(lv->datatype)) {
+ _set_error("Expected type for variable.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
tokenizer->advance();
@@ -2499,26 +2714,36 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
- lv->assign = subexpr;
+ lv->assignments++;
assigned = subexpr;
} else {
ConstantNode *c = alloc_node<ConstantNode>();
- c->value = Variant();
+ if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) {
+ Variant::CallError err;
+ c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err);
+ } else {
+ c->value = Variant();
+ }
+ c->line = var_line;
assigned = c;
}
//must be added later, to avoid self-referencing.
- p_block->variables.push_back(n); //line?
- p_block->variable_lines.push_back(var_line);
+ p_block->variables.insert(n, lv);
IdentifierNode *id = alloc_node<IdentifierNode>();
id->name = n;
+ id->declared_block = p_block;
+ id->line = var_line;
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_ASSIGN;
op->arguments.push_back(id);
op->arguments.push_back(assigned);
+ op->line = var_line;
p_block->statements.push_back(op);
+ lv->assign_op = op;
+ lv->assign = assigned;
if (!_end_statement()) {
_set_error("Expected end of statement (var)");
@@ -2563,6 +2788,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
p_block->statements.push_back(cf_if);
+ bool all_have_return = cf_if->body->has_return;
+ bool have_else = false;
+
while (true) {
while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline())
@@ -2619,6 +2847,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
+ all_have_return = all_have_return && cf_else->body->has_return;
+
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
if (tab_level.back()->get() > indent_level) {
@@ -2642,12 +2872,19 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
+ all_have_return = all_have_return && cf_if->body_else->has_return;
+ have_else = true;
+
break; //after else, exit
} else
break;
}
+ cf_if->body->has_return = all_have_return;
+ // If there's no else block, path out of the if might not have a return
+ p_block->has_return = all_have_return && have_else;
+
} break;
case GDScriptTokenizer::TK_CF_WHILE: {
@@ -2680,6 +2917,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
current_block = p_block;
if (error_set)
return;
+ p_block->has_return = cf_while->body->has_return;
p_block->statements.push_back(cf_while);
} break;
case GDScriptTokenizer::TK_CF_FOR: {
@@ -2711,6 +2949,9 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
+ DataType iter_type;
+ iter_type.is_constant = true;
+
if (container->type == Node::TYPE_OPERATOR) {
OperatorNode *op = static_cast<OperatorNode *>(container);
@@ -2745,6 +2986,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
case 2: cn->value = Vector2(constants[0], constants[1]); break;
case 3: cn->value = Vector3(constants[0], constants[1], constants[2]); break;
}
+ cn->datatype = _type_from_variant(cn->value);
container = cn;
} else {
OperatorNode *on = alloc_node<OperatorNode>();
@@ -2766,6 +3008,10 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
container = on;
}
}
+
+ iter_type.has_type = true;
+ iter_type.kind = DataType::BUILTIN;
+ iter_type.builtin_type = Variant::INT;
}
}
@@ -2789,15 +3035,20 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
// this is for checking variable for redefining
// inside this _parse_block
- cf_for->body->variables.push_back(id->name);
- cf_for->body->variable_lines.push_back(id->line);
+ LocalVarNode *lv = alloc_node<LocalVarNode>();
+ lv->name = id->name;
+ lv->line = id->line;
+ lv->assignments++;
+ id->declared_block = cf_for->body;
+ lv->set_datatype(iter_type);
+ id->set_datatype(iter_type);
+ cf_for->body->variables.insert(id->name, lv);
_parse_block(cf_for->body, p_static);
- cf_for->body->variables.remove(0);
- cf_for->body->variable_lines.remove(0);
current_block = p_block;
if (error_set)
return;
+ p_block->has_return = cf_for->body->has_return;
p_block->statements.push_back(cf_for);
} break;
case GDScriptTokenizer::TK_CF_CONTINUE: {
@@ -2827,6 +3078,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
tokenizer->advance();
ControlFlowNode *cf_return = alloc_node<ControlFlowNode>();
cf_return->cf_type = ControlFlowNode::CF_RETURN;
+ cf_return->line = tokenizer->get_token_line(-1);
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON || tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE || tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
//expect end of statement
@@ -2850,6 +3102,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
return;
}
}
+ p_block->has_return = true;
} break;
case GDScriptTokenizer::TK_CF_MATCH: {
@@ -2882,12 +3135,14 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
_parse_pattern_block(compiled_branches, match_node->branches, p_static);
- _transform_match_statment(compiled_branches, match_node);
+ if (error_set) return;
ControlFlowNode *match_cf_node = alloc_node<ControlFlowNode>();
match_cf_node->cf_type = ControlFlowNode::CF_MATCH;
match_cf_node->match = match_node;
+ match_cf_node->body = compiled_branches;
+ p_block->has_return = p_block->has_return || compiled_branches->has_return;
p_block->statements.push_back(match_cf_node);
_end_statement();
@@ -2938,16 +3193,6 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
} break;
- /*
- case GDScriptTokenizer::TK_CF_LOCAL: {
-
- if (tokenizer->get_token(1)!=GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1)!=GDScriptTokenizer::TK_NEWLINE ) {
-
- _set_error("Expected ';' or <NewLine>.");
- }
- tokenizer->advance();
- } break;
- */
}
}
}
@@ -3087,6 +3332,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
switch (token) {
+ case GDScriptTokenizer::TK_CURSOR: {
+ tokenizer->advance();
+ } break;
case GDScriptTokenizer::TK_EOF:
p_class->end_line = tokenizer->get_token_line();
case GDScriptTokenizer::TK_ERROR: {
@@ -3103,6 +3351,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
} break;
case GDScriptTokenizer::TK_PR_EXTENDS: {
+ _mark_line_as_safe(tokenizer->get_token_line());
_parse_extends(p_class);
if (error_set)
return;
@@ -3112,6 +3361,33 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
} break;
+ case GDScriptTokenizer::TK_PR_CLASS_NAME: {
+
+ if (p_class->owner) {
+ _set_error("'class_name' is only valid for the main class namespace.");
+ return;
+ }
+ if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
+
+ _set_error("'class_name' syntax: 'class_name <UniqueName>'");
+ return;
+ }
+
+ p_class->name = tokenizer->get_token_identifier(1);
+
+ if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
+ _set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+ return;
+ }
+
+ if (ClassDB::class_exists(p_class->name)) {
+ _set_error("Class '" + p_class->name + "' shadows a native class.");
+ return;
+ }
+
+ tokenizer->advance(2);
+
+ } break;
case GDScriptTokenizer::TK_PR_TOOL: {
if (p_class->tool) {
@@ -3128,7 +3404,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
//class inside class :D
StringName name;
- StringName extends;
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
@@ -3138,6 +3413,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
name = tokenizer->get_token_identifier(1);
tokenizer->advance(2);
+ // Check if name is shadowing something else
+ if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
+ _set_error("Class '" + String(name) + "' shadows a native class.");
+ return;
+ }
+ if (ScriptServer::is_global_class(name)) {
+ _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+ return;
+ }
+ ClassNode *outer_class = p_class;
+ while (outer_class) {
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i]->name == name) {
+ _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
+ if (outer_class->constant_expressions.has(name)) {
+ _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
+ return;
+ }
+
+ outer_class = outer_class->owner;
+ }
+
ClassNode *newclass = alloc_node<ClassNode>();
newclass->initializer = alloc_node<BlockNode>();
newclass->initializer->parent_class = newclass;
@@ -3222,6 +3522,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
Vector<StringName> arguments;
+ Vector<DataType> argument_types;
Vector<Node *> default_values;
int fnline = tokenizer->get_token_line();
@@ -3252,6 +3553,18 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
+ DataType argtype;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ argtype.infer_type = true;
+ tokenizer->advance();
+ } else if (!_parse_type(argtype)) {
+ _set_error("Expected type for argument.");
+ return;
+ }
+ }
+ argument_types.push_back(argtype);
+
if (defaulting && tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
_set_error("Default parameter expected.");
@@ -3269,9 +3582,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
OperatorNode *on = alloc_node<OperatorNode>();
on->op = OperatorNode::OP_ASSIGN;
+ on->line = fnline;
IdentifierNode *in = alloc_node<IdentifierNode>();
in->name = argname;
+ in->line = fnline;
on->arguments.push_back(in);
on->arguments.push_back(defval);
@@ -3308,6 +3623,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (name == "_init") {
+ if (_static) {
+ _set_error("Constructor cannot be static.");
+ return;
+ }
+
if (p_class->extends_used) {
OperatorNode *cparent = alloc_node<OperatorNode>();
@@ -3322,6 +3642,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
_set_error("expected '(' for parent constructor arguments.");
+ return;
}
tokenizer->advance();
@@ -3359,6 +3680,15 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}
+ DataType return_type;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) {
+
+ if (!_parse_type(return_type, true)) {
+ _set_error("Expected return type for function.");
+ return;
+ }
+ }
+
if (!_enter_indent_block(block)) {
_set_error("Indented block expected.");
@@ -3367,7 +3697,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
FunctionNode *function = alloc_node<FunctionNode>();
function->name = name;
+ function->return_type = return_type;
function->arguments = arguments;
+ function->argument_types = argument_types;
function->default_values = default_values;
function->_static = _static;
function->line = fnline;
@@ -3861,6 +4193,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
current_export.hint_string = native_class->get_name();
+ current_export.class_name = native_class->get_name();
} else {
current_export = PropertyInfo();
@@ -4082,10 +4415,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member.line = tokenizer->get_token_line();
member.rpc_mode = rpc_mode;
+ if (current_class->constant_expressions.has(member.identifier)) {
+ _set_error("A constant named '" + String(member.identifier) + "' alread exists in this class (at line: " +
+ itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
+ return;
+ }
+
+ for (int i = 0; i < current_class->variables.size(); i++) {
+ if (current_class->variables[i].identifier == member.identifier) {
+ _set_error("Variable '" + String(member.identifier) + "' alread exists in this class (at line: " +
+ itos(current_class->variables[i].line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED;
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ member.data_type = DataType();
+#ifdef DEBUG_ENABLED
+ member.data_type.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(member.data_type)) {
+ _set_error("Expected type for class variable.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_OP_ASSIGN) {
#ifdef DEBUG_ENABLED
@@ -4093,7 +4453,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
#endif
tokenizer->advance();
- Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport);
+ Node *subexpr = _parse_and_reduce_expression(p_class, false, autoexport || member._export.type != Variant::NIL);
if (!subexpr) {
if (_recover_from_completion()) {
break;
@@ -4117,42 +4477,31 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member.expression = subexpr;
- if (autoexport) {
- if (1) /*(subexpr->type==Node::TYPE_ARRAY) {
-
- member._export.type=Variant::ARRAY;
-
- } else if (subexpr->type==Node::TYPE_DICTIONARY) {
+ if (autoexport && !member.data_type.has_type) {
- member._export.type=Variant::DICTIONARY;
-
- } else*/
- {
-
- if (subexpr->type != Node::TYPE_CONSTANT) {
+ if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Type-less export needs a constant expression assigned to infer type.");
- return;
- }
+ _set_error("Type-less export needs a constant expression assigned to infer type.");
+ return;
+ }
- ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
- if (cn->value.get_type() == Variant::NIL) {
+ ConstantNode *cn = static_cast<ConstantNode *>(subexpr);
+ if (cn->value.get_type() == Variant::NIL) {
- _set_error("Can't accept a null constant expression for inferring export type.");
+ _set_error("Can't accept a null constant expression for inferring export type.");
+ return;
+ }
+ member._export.type = cn->value.get_type();
+ member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ if (cn->value.get_type() == Variant::OBJECT) {
+ Object *obj = cn->value;
+ Resource *res = Object::cast_to<Resource>(obj);
+ if (res == NULL) {
+ _set_error("Exported constant not a type or resource.");
return;
}
- member._export.type = cn->value.get_type();
- member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
- if (cn->value.get_type() == Variant::OBJECT) {
- Object *obj = cn->value;
- Resource *res = Object::cast_to<Resource>(obj);
- if (res == NULL) {
- _set_error("Exported constant not a type or resource.");
- return;
- }
- member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
- member._export.hint_string = res->get_class();
- }
+ member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ member._export.hint_string = res->get_class();
}
}
#ifdef TOOLS_ENABLED
@@ -4186,15 +4535,37 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
else
p_class->initializer->statements.push_back(op);
- } else {
+ member.initial_assignment = op;
- if (autoexport) {
+ } else {
+ if (autoexport && !member.data_type.has_type) {
_set_error("Type-less export needs a constant expression assigned to infer type.");
return;
}
}
+ if (autoexport && member.data_type.has_type) {
+ if (member.data_type.kind == DataType::BUILTIN) {
+ member._export.type = member.data_type.builtin_type;
+ } else if (member.data_type.kind == DataType::NATIVE) {
+ if (ClassDB::is_parent_class(member.data_type.native_type, "Resource")) {
+ member._export.type = Variant::OBJECT;
+ member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
+ member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
+ member._export.hint_string = member.data_type.native_type;
+ member._export.class_name = member.data_type.native_type;
+ } else {
+ _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
+ return;
+ }
+
+ } else {
+ _set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
+ return;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) {
tokenizer->advance();
@@ -4231,7 +4602,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
} break;
case GDScriptTokenizer::TK_PR_CONST: {
- //variale declaration and (eventual) initialization
+ // constant declaration and initialization
ClassNode::Constant constant;
@@ -4242,9 +4613,38 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
- constant.identifier = tokenizer->get_token_literal();
+ StringName const_id = tokenizer->get_token_literal();
+ int line = tokenizer->get_token_line();
+
+ if (current_class->constant_expressions.has(const_id)) {
+ _set_error("Constant '" + String(const_id) + "' alread exists in this class (at line: " +
+ itos(current_class->constant_expressions[const_id].expression->line) + ").");
+ return;
+ }
+
+ for (int i = 0; i < current_class->variables.size(); i++) {
+ if (current_class->variables[i].identifier == const_id) {
+ _set_error("A variable named '" + String(const_id) + "' alread exists in this class (at line: " +
+ itos(current_class->variables[i].line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
+ if (tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ constant.type = DataType();
+#ifdef DEBUG_ENABLED
+ constant.type.infer_type = true;
+#endif
+ tokenizer->advance();
+ } else if (!_parse_type(constant.type)) {
+ _set_error("Expected type for class constant.");
+ return;
+ }
+ }
+
if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
_set_error("Constant expects assignment.");
return;
@@ -4261,14 +4661,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected constant expression");
+ _set_error("Expected constant expression", line);
+ return;
}
+ subexpr->line = line;
constant.expression = subexpr;
- p_class->constant_expressions.push_back(constant);
+ p_class->constant_expressions.insert(const_id, constant);
if (!_end_statement()) {
- _set_error("Expected end of statement (constant)");
+ _set_error("Expected end of statement (constant)", line);
return;
}
@@ -4311,7 +4713,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
} else { // tokenizer->is_token_literal(0, true)
ClassNode::Constant constant;
- constant.identifier = tokenizer->get_token_literal();
+ StringName const_id = tokenizer->get_token_literal();
tokenizer->advance();
@@ -4328,22 +4730,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (subexpr->type != Node::TYPE_CONSTANT) {
_set_error("Expected constant expression");
+ return;
}
- const ConstantNode *subexpr_const = static_cast<const ConstantNode *>(subexpr);
+ ConstantNode *subexpr_const = static_cast<ConstantNode *>(subexpr);
if (subexpr_const->value.get_type() != Variant::INT) {
_set_error("Expected an int value for enum");
+ return;
}
last_assign = subexpr_const->value;
- constant.expression = subexpr;
+ constant.expression = subexpr_const;
} else {
last_assign = last_assign + 1;
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = last_assign;
+ cn->datatype = _type_from_variant(cn->value);
constant.expression = cn;
}
@@ -4353,20 +4758,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (enum_name != "") {
const ConstantNode *cn = static_cast<const ConstantNode *>(constant.expression);
- enum_dict[constant.identifier] = cn->value;
+ enum_dict[const_id] = cn->value;
}
- p_class->constant_expressions.push_back(constant);
+ constant.type.has_type = true;
+ constant.type.kind = DataType::BUILTIN;
+ constant.type.builtin_type = Variant::INT;
+ p_class->constant_expressions.insert(const_id, constant);
}
}
if (enum_name != "") {
ClassNode::Constant enum_constant;
- enum_constant.identifier = enum_name;
ConstantNode *cn = alloc_node<ConstantNode>();
cn->value = enum_dict;
+ cn->datatype = _type_from_variant(cn->value);
+
enum_constant.expression = cn;
- p_class->constant_expressions.push_back(enum_constant);
+ enum_constant.type = cn->datatype;
+ p_class->constant_expressions.insert(enum_name, enum_constant);
}
if (!_end_statement()) {
@@ -4396,6 +4806,2747 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}
+void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
+
+ if (p_class->extends_used) {
+ //do inheritance
+ String path = p_class->extends_file;
+
+ Ref<GDScript> script;
+ StringName native;
+ ClassNode *base_class = NULL;
+
+ if (path != "") {
+ //path (and optionally subclasses)
+
+ if (path.is_rel_path()) {
+
+ String base = base_path;
+
+ if (base == "" || base.is_rel_path()) {
+ _set_error("Could not resolve relative path for parent class: " + path, p_class->line);
+ return;
+ }
+ path = base.plus_file(path).simplify_path();
+ }
+ script = ResourceLoader::load(path);
+ if (script.is_null()) {
+ _set_error("Could not load base class: " + path, p_class->line);
+ return;
+ }
+ if (!script->is_valid()) {
+
+ _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line);
+ return;
+ }
+
+ if (p_class->extends_class.size()) {
+
+ for (int i = 0; i < p_class->extends_class.size(); i++) {
+
+ String sub = p_class->extends_class[i];
+ if (script->get_subclasses().has(sub)) {
+
+ Ref<Script> subclass = script->get_subclasses()[sub]; //avoid reference from disappearing
+ script = subclass;
+ } else {
+
+ _set_error("Could not find subclass: " + sub, p_class->line);
+ return;
+ }
+ }
+ }
+
+ } else {
+
+ if (p_class->extends_class.size() == 0) {
+ _set_error("Parser bug: undecidable inheritance.", p_class->line);
+ ERR_FAIL();
+ }
+ //look around for the subclasses
+
+ int extend_iter = 1;
+ String base = p_class->extends_class[0];
+ ClassNode *p = p_class->owner;
+ Ref<GDScript> base_script;
+
+ if (ScriptServer::is_global_class(base)) {
+ base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
+ if (!base_script.is_valid()) {
+ _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
+ return;
+ }
+ p = NULL;
+ }
+
+ while (p) {
+
+ bool found = false;
+
+ for (int i = 0; i < p->subclasses.size(); i++) {
+ if (p->subclasses[i]->name == base) {
+ ClassNode *test = p->subclasses[i];
+ while (test) {
+ if (test == p_class) {
+ _set_error("Cyclic inheritance.", test->line);
+ return;
+ }
+ if (test->base_type.kind == DataType::CLASS) {
+ test = test->base_type.class_type;
+ } else {
+ break;
+ }
+ }
+ found = true;
+ if (extend_iter < p_class->extends_class.size()) {
+ // Keep looking at current classes if possible
+ base = p_class->extends_class[extend_iter++];
+ p = p->subclasses[i];
+ } else {
+ base_class = p->subclasses[i];
+ }
+ break;
+ }
+ }
+
+ if (base_class) break;
+ if (found) continue;
+
+ if (p->constant_expressions.has(base)) {
+ if (!p->constant_expressions[base].expression->type == Node::TYPE_CONSTANT) {
+ _set_error("Could not resolve constant '" + base + "'.", p_class->line);
+ return;
+ }
+ const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
+ base_script = cn->value;
+ if (base_script.is_null()) {
+ _set_error("Constant is not a class: " + base, p_class->line);
+ return;
+ }
+ break;
+ }
+
+ p = p->owner;
+ }
+
+ if (base_script.is_valid()) {
+
+ String ident = base;
+
+ for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
+
+ String subclass = p_class->extends_class[i];
+
+ ident += ("." + subclass);
+
+ if (base_script->get_subclasses().has(subclass)) {
+
+ base_script = base_script->get_subclasses()[subclass];
+ } else if (base_script->get_constants().has(subclass)) {
+
+ Ref<GDScript> new_base_class = base_script->get_constants()[subclass];
+ if (new_base_class.is_null()) {
+ _set_error("Constant is not a class: " + ident, p_class->line);
+ return;
+ }
+ base_script = new_base_class;
+ } else {
+
+ _set_error("Could not find subclass: " + ident, p_class->line);
+ return;
+ }
+ }
+
+ script = base_script;
+
+ } else if (!base_class) {
+
+ if (p_class->extends_class.size() > 1) {
+
+ _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line);
+ return;
+ }
+ //if not found, try engine classes
+ if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
+
+ _set_error("Unknown class: '" + base + "'", p_class->line);
+ return;
+ }
+
+ native = base;
+ }
+ }
+
+ if (base_class) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::CLASS;
+ p_class->base_type.class_type = base_class;
+ } else if (script.is_valid()) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::GDSCRIPT;
+ p_class->base_type.script_type = script;
+ p_class->base_type.native_type = script->get_instance_base_type();
+ } else if (native != StringName()) {
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::NATIVE;
+ p_class->base_type.native_type = native;
+ } else {
+ _set_error("Could not determine inheritance", p_class->line);
+ return;
+ }
+
+ } else {
+ // without extends, implicitly extend Reference
+ p_class->base_type.has_type = true;
+ p_class->base_type.kind = DataType::NATIVE;
+ p_class->base_type.native_type = "Reference";
+ }
+
+ // Recursively determine subclasses
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ _determine_inheritance(p_class->subclasses[i]);
+ }
+}
+
+String GDScriptParser::DataType::to_string() const {
+ if (!has_type) return "var";
+ switch (kind) {
+ case BUILTIN: {
+ if (builtin_type == Variant::NIL) return "null";
+ return Variant::get_type_name(builtin_type);
+ } break;
+ case NATIVE: {
+ if (is_meta_type) {
+ return "GDScriptNativeClass";
+ }
+ return native_type.operator String();
+ } break;
+
+ case GDSCRIPT: {
+ Ref<GDScript> gds = script_type;
+ const String &gds_class = gds->get_script_class_name();
+ if (!gds_class.empty()) {
+ return gds_class;
+ }
+ } // fallthrough
+ case SCRIPT: {
+ if (is_meta_type) {
+ return script_type->get_class_name().operator String();
+ }
+ String name = script_type->get_name();
+ if (name != String()) {
+ return name;
+ }
+ name = script_type->get_path().get_file();
+ if (name != String()) {
+ return name;
+ }
+ return native_type.operator String();
+ } break;
+ case CLASS: {
+ ERR_FAIL_COND_V(!class_type, String());
+ if (is_meta_type) {
+ return "GDScript";
+ }
+ if (class_type->name == StringName()) {
+ return "self";
+ }
+ return class_type->name.operator String();
+ } break;
+ }
+
+ return "Unresolved";
+}
+
+bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
+ tokenizer->advance();
+ r_type.has_type = true;
+
+ bool finished = false;
+ bool can_index = false;
+ String full_name;
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
+ completion_cursor = StringName();
+ completion_type = COMPLETION_TYPE_HINT;
+ completion_class = current_class;
+ completion_function = current_function;
+ completion_line = tokenizer->get_token_line();
+ completion_argument = 0;
+ completion_block = current_block;
+ completion_found = true;
+ completion_ident_is_call = p_can_be_void;
+ tokenizer->advance();
+ }
+
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_PR_VOID: {
+ if (!p_can_be_void) {
+ return false;
+ }
+ r_type.kind = DataType::BUILTIN;
+ r_type.builtin_type = Variant::NIL;
+ } break;
+ case GDScriptTokenizer::TK_BUILT_IN_TYPE: {
+ r_type.builtin_type = tokenizer->get_token_type();
+ if (tokenizer->get_token_type() == Variant::OBJECT) {
+ r_type.kind = DataType::NATIVE;
+ r_type.native_type = "Object";
+ } else {
+ r_type.kind = DataType::BUILTIN;
+ }
+ } break;
+ case GDScriptTokenizer::TK_IDENTIFIER: {
+ r_type.native_type = tokenizer->get_token_identifier();
+ if (ClassDB::class_exists(r_type.native_type) || ClassDB::class_exists("_" + r_type.native_type.operator String())) {
+ r_type.kind = DataType::NATIVE;
+ } else {
+ r_type.kind = DataType::UNRESOLVED;
+ can_index = true;
+ full_name = r_type.native_type;
+ }
+ } break;
+ default: {
+ return false;
+ }
+ }
+
+ tokenizer->advance();
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_CURSOR) {
+ completion_cursor = r_type.native_type;
+ completion_type = COMPLETION_TYPE_HINT;
+ completion_class = current_class;
+ completion_function = current_function;
+ completion_line = tokenizer->get_token_line();
+ completion_argument = 0;
+ completion_block = current_block;
+ completion_found = true;
+ completion_ident_is_call = p_can_be_void;
+ tokenizer->advance();
+ }
+
+ if (can_index) {
+ while (!finished) {
+ switch (tokenizer->get_token()) {
+ case GDScriptTokenizer::TK_PERIOD: {
+ if (!can_index) {
+ _set_error("Unexpected '.'.");
+ return false;
+ }
+ can_index = false;
+ tokenizer->advance();
+ } break;
+ case GDScriptTokenizer::TK_IDENTIFIER: {
+ if (can_index) {
+ _set_error("Unexpected identifier.");
+ return false;
+ }
+
+ StringName id;
+ bool has_completion = _get_completable_identifier(COMPLETION_TYPE_HINT_INDEX, id);
+ if (id == StringName()) {
+ id = "@temp";
+ }
+
+ full_name += "." + id.operator String();
+ can_index = true;
+ if (has_completion) {
+ completion_cursor = full_name;
+ }
+ } break;
+ default: {
+ finished = true;
+ } break;
+ }
+ }
+
+ if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) {
+ _set_error("Expected subclass identifier.");
+ return false;
+ }
+
+ r_type.native_type = full_name;
+ }
+
+ return true;
+}
+
+GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source, int p_line) {
+ if (!p_source.has_type) return p_source;
+ if (p_source.kind != DataType::UNRESOLVED) return p_source;
+
+ Vector<String> full_name = p_source.native_type.operator String().split(".", false);
+ int name_part = 0;
+
+ DataType result;
+ result.has_type = true;
+
+ while (name_part < full_name.size()) {
+
+ bool found = false;
+ StringName id = full_name[name_part];
+ DataType base_type = result;
+
+ ClassNode *p = NULL;
+ if (name_part == 0) {
+ if (ScriptServer::is_global_class(id)) {
+ String script_path = ScriptServer::get_global_class_path(id);
+ if (script_path == self_path) {
+ result.kind = DataType::CLASS;
+ result.class_type = current_class;
+ } else {
+ Ref<Script> script = ResourceLoader::load(script_path);
+ Ref<GDScript> gds = script;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ } else if (script.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = script;
+ } else {
+ _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line);
+ return DataType();
+ }
+ }
+ name_part++;
+ continue;
+ } else {
+ p = current_class;
+ }
+ } else if (base_type.kind == DataType::CLASS) {
+ p = base_type.class_type;
+ }
+ while (p) {
+ if (p->constant_expressions.has(id)) {
+ if (p->constant_expressions[id].expression->type != Node::TYPE_CONSTANT) {
+ _set_error("Parser bug: unresolved constant.", p_line);
+ ERR_FAIL_V(result);
+ }
+ const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[id].expression);
+ Ref<GDScript> gds = cn->value;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ found = true;
+ } else {
+ Ref<Script> scr = cn->value;
+ if (scr.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr;
+ found = true;
+ }
+ }
+ break;
+ }
+
+ // Inner classes
+ ClassNode *outer_class = p;
+ while (outer_class) {
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i] == p) {
+ continue;
+ }
+ if (outer_class->subclasses[i]->name == id) {
+ found = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class->subclasses[i];
+ break;
+ }
+ }
+ if (found) {
+ break;
+ }
+ outer_class = outer_class->owner;
+ }
+
+ if (!found && p->base_type.kind == DataType::CLASS) {
+ p = p->base_type.class_type;
+ } else {
+ base_type = p->base_type;
+ break;
+ }
+ }
+
+ // Still look for class constants in parent script
+ if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
+ Ref<Script> scr = base_type.script_type;
+ ERR_FAIL_COND_V(scr.is_null(), result);
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+
+ if (constants.has(id)) {
+ Ref<GDScript> gds = constants[id];
+
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ found = true;
+ } else {
+ Ref<Script> scr = constants[id];
+ if (scr.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr;
+ found = true;
+ }
+ }
+ }
+ }
+
+ if (!found && !for_completion) {
+ String base;
+ if (name_part == 0) {
+ base = "self";
+ } else {
+ base = result.to_string();
+ }
+ _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" +
+ base + "'.",
+ p_line);
+ return DataType();
+ }
+
+ name_part++;
+ }
+
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_variant(const Variant &p_value) const {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.kind = DataType::BUILTIN;
+ result.builtin_type = p_value.get_type();
+
+ if (result.builtin_type == Variant::OBJECT) {
+ Object *obj = p_value.operator Object *();
+ if (!obj) {
+ return DataType();
+ }
+ result.native_type = obj->get_class_name();
+ Ref<Script> scr = p_value;
+ if (scr.is_valid()) {
+ result.is_meta_type = true;
+ } else {
+ result.is_meta_type = false;
+ scr = obj->get_script();
+ }
+ if (scr.is_valid()) {
+ result.script_type = scr;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ result.native_type = scr->get_instance_base_type();
+ } else {
+ result.kind = DataType::NATIVE;
+ }
+ }
+
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo &p_property, bool p_nil_is_variant) const {
+ DataType ret;
+ if (p_property.type == Variant::NIL && (p_nil_is_variant || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) {
+ // Variant
+ return ret;
+ }
+ ret.has_type = true;
+ ret.builtin_type = p_property.type;
+ if (p_property.type == Variant::OBJECT) {
+ ret.kind = DataType::NATIVE;
+ ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
+ } else {
+ ret.kind = DataType::BUILTIN;
+ }
+ return ret;
+}
+
+GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataType &p_gdtype) const {
+ DataType result;
+ if (!p_gdtype.has_type) {
+ return result;
+ }
+
+ result.has_type = true;
+ result.builtin_type = p_gdtype.builtin_type;
+ result.native_type = p_gdtype.native_type;
+ result.script_type = p_gdtype.script_type;
+
+ switch (p_gdtype.kind) {
+ case GDScriptDataType::BUILTIN: {
+ result.kind = DataType::BUILTIN;
+ } break;
+ case GDScriptDataType::NATIVE: {
+ result.kind = DataType::NATIVE;
+ } break;
+ case GDScriptDataType::GDSCRIPT: {
+ result.kind = DataType::GDSCRIPT;
+ } break;
+ case GDScriptDataType::SCRIPT: {
+ result.kind = DataType::SCRIPT;
+ } break;
+ }
+ return result;
+}
+
+GDScriptParser::DataType GDScriptParser::_get_operation_type(const Variant::Operator p_op, const DataType &p_a, const DataType &p_b, bool &r_valid) const {
+ if (!p_a.has_type || !p_b.has_type) {
+ r_valid = true;
+ return DataType();
+ }
+
+ Variant::Type a_type = p_a.kind == DataType::BUILTIN ? p_a.builtin_type : Variant::OBJECT;
+ Variant::Type b_type = p_b.kind == DataType::BUILTIN ? p_b.builtin_type : Variant::OBJECT;
+
+ Variant a;
+ REF a_ref;
+ if (a_type == Variant::OBJECT) {
+ a_ref.instance();
+ a = a_ref;
+ } else {
+ Variant::CallError err;
+ a = Variant::construct(a_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ r_valid = false;
+ return DataType();
+ }
+ }
+ Variant b;
+ REF b_ref;
+ if (b_type == Variant::OBJECT) {
+ b_ref.instance();
+ b = b_ref;
+ } else {
+ Variant::CallError err;
+ b = Variant::construct(b_type, NULL, 0, err);
+ if (err.error != Variant::CallError::CALL_OK) {
+ r_valid = false;
+ return DataType();
+ }
+ }
+
+ // Avoid division by zero
+ if (a_type == Variant::INT || a_type == Variant::REAL) {
+ Variant::evaluate(Variant::OP_ADD, a, 1, a, r_valid);
+ }
+ if (b_type == Variant::INT || b_type == Variant::REAL) {
+ Variant::evaluate(Variant::OP_ADD, b, 1, b, r_valid);
+ }
+ if (a_type == Variant::STRING && b_type != Variant::ARRAY) {
+ a = "%s"; // Work around for formatting operator (%)
+ }
+
+ Variant ret;
+ Variant::evaluate(p_op, a, b, ret, r_valid);
+
+ if (r_valid) {
+ return _type_from_variant(ret);
+ }
+
+ return DataType();
+}
+
+Variant::Operator GDScriptParser::_get_variant_operation(const OperatorNode::Operator &p_op) const {
+ switch (p_op) {
+ case OperatorNode::OP_NEG: {
+ return Variant::OP_NEGATE;
+ } break;
+ case OperatorNode::OP_POS: {
+ return Variant::OP_POSITIVE;
+ } break;
+ case OperatorNode::OP_NOT: {
+ return Variant::OP_NOT;
+ } break;
+ case OperatorNode::OP_BIT_INVERT: {
+ return Variant::OP_BIT_NEGATE;
+ } break;
+ case OperatorNode::OP_IN: {
+ return Variant::OP_IN;
+ } break;
+ case OperatorNode::OP_EQUAL: {
+ return Variant::OP_EQUAL;
+ } break;
+ case OperatorNode::OP_NOT_EQUAL: {
+ return Variant::OP_NOT_EQUAL;
+ } break;
+ case OperatorNode::OP_LESS: {
+ return Variant::OP_LESS;
+ } break;
+ case OperatorNode::OP_LESS_EQUAL: {
+ return Variant::OP_LESS_EQUAL;
+ } break;
+ case OperatorNode::OP_GREATER: {
+ return Variant::OP_GREATER;
+ } break;
+ case OperatorNode::OP_GREATER_EQUAL: {
+ return Variant::OP_GREATER_EQUAL;
+ } break;
+ case OperatorNode::OP_AND: {
+ return Variant::OP_AND;
+ } break;
+ case OperatorNode::OP_OR: {
+ return Variant::OP_OR;
+ } break;
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ADD: {
+ return Variant::OP_ADD;
+ } break;
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_SUB: {
+ return Variant::OP_SUBTRACT;
+ } break;
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_MUL: {
+ return Variant::OP_MULTIPLY;
+ } break;
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_DIV: {
+ return Variant::OP_DIVIDE;
+ } break;
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_MOD: {
+ return Variant::OP_MODULE;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_BIT_AND: {
+ return Variant::OP_BIT_AND;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_BIT_OR: {
+ return Variant::OP_BIT_OR;
+ } break;
+ case OperatorNode::OP_ASSIGN_BIT_XOR:
+ case OperatorNode::OP_BIT_XOR: {
+ return Variant::OP_BIT_XOR;
+ } break;
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_SHIFT_LEFT: {
+ return Variant::OP_SHIFT_LEFT;
+ }
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_SHIFT_RIGHT: {
+ return Variant::OP_SHIFT_RIGHT;
+ }
+ default: {
+ return Variant::OP_MAX;
+ } break;
+ }
+}
+
+bool GDScriptParser::_is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion) const {
+ // Ignore for completion
+ if (!check_types || for_completion) {
+ return true;
+ }
+ // Can't test if not all have type
+ if (!p_container.has_type || !p_expression.has_type) {
+ return true;
+ }
+
+ // Should never get here unresolved
+ ERR_FAIL_COND_V(p_container.kind == DataType::UNRESOLVED, false);
+ ERR_FAIL_COND_V(p_expression.kind == DataType::UNRESOLVED, false);
+
+ if (p_container.kind == DataType::BUILTIN && p_expression.kind == DataType::BUILTIN) {
+ bool valid = p_container.builtin_type == p_expression.builtin_type;
+ if (p_allow_implicit_conversion) {
+ valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::REAL);
+ valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::INT);
+ valid = valid || (p_container.builtin_type == Variant::STRING && p_expression.builtin_type == Variant::NODE_PATH);
+ valid = valid || (p_container.builtin_type == Variant::NODE_PATH && p_expression.builtin_type == Variant::STRING);
+ valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::REAL);
+ valid = valid || (p_container.builtin_type == Variant::BOOL && p_expression.builtin_type == Variant::INT);
+ valid = valid || (p_container.builtin_type == Variant::INT && p_expression.builtin_type == Variant::BOOL);
+ valid = valid || (p_container.builtin_type == Variant::REAL && p_expression.builtin_type == Variant::BOOL);
+ }
+ return valid;
+ }
+
+ if (p_container.kind == DataType::BUILTIN || (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type != Variant::NIL)) {
+ // Can't mix built-ins with objects
+ return false;
+ }
+
+ // From now on everything is objects, check polymorphism
+ // The container must be the same class or a superclass of the expression
+
+ if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
+ // Null can be assigned to object types
+ return true;
+ }
+
+ StringName expr_native;
+ Ref<Script> expr_script;
+ ClassNode *expr_class = NULL;
+
+ switch (p_expression.kind) {
+ case DataType::NATIVE: {
+ if (p_container.kind != DataType::NATIVE) {
+ // Non-native type can't be a superclass of a native type
+ return false;
+ }
+ if (p_expression.is_meta_type) {
+ expr_native = GDScriptNativeClass::get_class_static();
+ } else {
+ expr_native = p_expression.native_type;
+ }
+ } break;
+ case DataType::SCRIPT:
+ case DataType::GDSCRIPT: {
+ if (p_container.kind == DataType::CLASS) {
+ // This cannot be resolved without cyclic dependencies, so just bail out
+ return false;
+ }
+ if (p_expression.is_meta_type) {
+ expr_native = p_expression.script_type->get_class_name();
+ } else {
+ expr_script = p_expression.script_type;
+ expr_native = expr_script->get_instance_base_type();
+ }
+ } break;
+ case DataType::CLASS: {
+ if (p_expression.is_meta_type) {
+ expr_native = GDScript::get_class_static();
+ } else {
+ expr_class = p_expression.class_type;
+ ClassNode *base = expr_class;
+ while (base->base_type.kind == DataType::CLASS) {
+ base = base->base_type.class_type;
+ }
+ expr_native = base->base_type.native_type;
+ expr_script = base->base_type.script_type;
+ }
+ }
+ }
+
+ switch (p_container.kind) {
+ case DataType::NATIVE: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
+ } else {
+ return ClassDB::is_parent_class(expr_native, p_container.native_type);
+ }
+ } break;
+ case DataType::SCRIPT:
+ case DataType::GDSCRIPT: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
+ }
+ if (expr_class == head && p_container.script_type->get_path() == self_path) {
+ // Special case: container is self script and expression is self
+ return true;
+ }
+ while (expr_script.is_valid()) {
+ if (expr_script == p_container.script_type) {
+ return true;
+ }
+ expr_script = expr_script->get_base_script();
+ }
+ return false;
+ } break;
+ case DataType::CLASS: {
+ if (p_container.is_meta_type) {
+ return ClassDB::is_parent_class(expr_native, GDScript::get_class_static());
+ }
+ if (p_container.class_type == head && expr_script.is_valid() && expr_script->get_path() == self_path) {
+ // Special case: container is self and expression is self script
+ return true;
+ }
+ while (expr_class) {
+ if (expr_class == p_container.class_type) {
+ return true;
+ }
+ expr_class = expr_class->base_type.class_type;
+ }
+ return false;
+ }
+ }
+
+ return false;
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
+ if (p_node->get_datatype().has_type) {
+ return p_node->get_datatype();
+ }
+
+ DataType node_type;
+
+ switch (p_node->type) {
+ case Node::TYPE_CONSTANT: {
+ node_type = _type_from_variant(static_cast<ConstantNode *>(p_node)->value);
+ } break;
+ case Node::TYPE_ARRAY: {
+ node_type.has_type = true;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::ARRAY;
+ } break;
+ case Node::TYPE_DICTIONARY: {
+ node_type.has_type = true;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::DICTIONARY;
+ } break;
+ case Node::TYPE_SELF: {
+ node_type.has_type = true;
+ node_type.kind = DataType::CLASS;
+ node_type.class_type = current_class;
+ } break;
+ case Node::TYPE_IDENTIFIER: {
+ IdentifierNode *id = static_cast<IdentifierNode *>(p_node);
+ if (id->declared_block) {
+ node_type = id->declared_block->variables[id->name]->get_datatype();
+ } else if (id->name == "#match_value") {
+ // It's a special id just for the match statetement, ignore
+ break;
+ } else if (current_function && current_function->arguments.find(id->name) >= 0) {
+ int idx = current_function->arguments.find(id->name);
+ node_type = current_function->argument_types[idx];
+ } else {
+ node_type = _reduce_identifier_type(NULL, id->name, id->line);
+ }
+ } break;
+ case Node::TYPE_CAST: {
+ CastNode *cn = static_cast<CastNode *>(p_node);
+
+ DataType source_type = _reduce_node_type(cn->source_node);
+ cn->cast_type = _resolve_type(cn->cast_type, cn->line);
+ if (source_type.has_type) {
+
+ bool valid = false;
+ if (check_types) {
+ if (cn->cast_type.kind == DataType::BUILTIN && source_type.kind == DataType::BUILTIN) {
+ valid = Variant::can_convert(source_type.builtin_type, cn->cast_type.builtin_type);
+ }
+ if (cn->cast_type.kind != DataType::BUILTIN && source_type.kind != DataType::BUILTIN) {
+ valid = _is_type_compatible(cn->cast_type, source_type) || _is_type_compatible(source_type, cn->cast_type);
+ }
+
+ if (!valid) {
+ _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() +
+ "' to '" + cn->cast_type.to_string() + "'.",
+ cn->line);
+ return DataType();
+ }
+ }
+ } else {
+ _mark_line_as_unsafe(cn->line);
+ }
+
+ node_type = cn->cast_type;
+
+ } break;
+ case Node::TYPE_OPERATOR: {
+ OperatorNode *op = static_cast<OperatorNode *>(p_node);
+
+ switch (op->op) {
+ case OperatorNode::OP_CALL:
+ case OperatorNode::OP_PARENT_CALL: {
+ node_type = _reduce_function_call_type(op);
+ } break;
+ case OperatorNode::OP_YIELD: {
+ if (op->arguments.size() == 2) {
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ DataType signal_type = _reduce_node_type(op->arguments[1]);
+ // TODO: Check if signal exists when it's a constant
+ if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
+ _set_error("First argument of 'yield()' must be an object.", op->line);
+ return DataType();
+ }
+ if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
+ _set_error("Second argument of 'yield()' must be a string.", op->line);
+ return DataType();
+ }
+ }
+ // yield can return anything
+ node_type.has_type = false;
+ } break;
+ case OperatorNode::OP_IS: {
+
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: binary operation without 2 arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType value_type = _reduce_node_type(op->arguments[0]);
+ DataType type_type = _reduce_node_type(op->arguments[1]);
+
+ if (check_types && type_type.has_type) {
+ if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
+ _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line);
+ return DataType();
+ }
+ type_type.is_meta_type = false; // Test the actual type
+ if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
+ // TODO: Make this a warning?
+ _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line);
+ return DataType();
+ }
+ }
+
+ node_type.has_type = true;
+ node_type.is_constant = true;
+ node_type.is_meta_type = false;
+ node_type.kind = DataType::BUILTIN;
+ node_type.builtin_type = Variant::BOOL;
+ } break;
+ // Unary operators
+ case OperatorNode::OP_NEG:
+ case OperatorNode::OP_POS:
+ case OperatorNode::OP_NOT:
+ case OperatorNode::OP_BIT_INVERT: {
+
+ DataType argument_type = _reduce_node_type(op->arguments[0]);
+ if (!argument_type.has_type) {
+ break;
+ }
+
+ Variant::Operator var_op = _get_variant_operation(op->op);
+ bool valid = false;
+ node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
+
+ if (check_types && !valid) {
+ _set_error("Invalid operand type ('" + argument_type.to_string() +
+ "') to unary operator '" + Variant::get_operator_name(var_op) + "'.",
+ op->line, op->column);
+ return DataType();
+ }
+
+ } break;
+ // Binary operators
+ case OperatorNode::OP_IN:
+ case OperatorNode::OP_EQUAL:
+ case OperatorNode::OP_NOT_EQUAL:
+ case OperatorNode::OP_LESS:
+ case OperatorNode::OP_LESS_EQUAL:
+ case OperatorNode::OP_GREATER:
+ case OperatorNode::OP_GREATER_EQUAL:
+ case OperatorNode::OP_AND:
+ case OperatorNode::OP_OR:
+ case OperatorNode::OP_ADD:
+ case OperatorNode::OP_SUB:
+ case OperatorNode::OP_MUL:
+ case OperatorNode::OP_DIV:
+ case OperatorNode::OP_MOD:
+ case OperatorNode::OP_SHIFT_LEFT:
+ case OperatorNode::OP_SHIFT_RIGHT:
+ case OperatorNode::OP_BIT_AND:
+ case OperatorNode::OP_BIT_OR:
+ case OperatorNode::OP_BIT_XOR: {
+
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: binary operation without 2 arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType argument_a_type = _reduce_node_type(op->arguments[0]);
+ DataType argument_b_type = _reduce_node_type(op->arguments[1]);
+ if (!argument_a_type.has_type || !argument_b_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ Variant::Operator var_op = _get_variant_operation(op->op);
+ bool valid = false;
+ node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
+
+ if (check_types && !valid) {
+ _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" +
+ argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.",
+ op->line, op->column);
+ return DataType();
+ }
+
+ } break;
+ // Ternary operators
+ case OperatorNode::OP_TERNARY_IF: {
+ if (op->arguments.size() != 3) {
+ _set_error("Parser bug: ternary operation without 3 arguments");
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType true_type = _reduce_node_type(op->arguments[1]);
+ DataType false_type = _reduce_node_type(op->arguments[2]);
+
+ // If types are equal, then the expression is of the same type
+ // If they are compatible, return the broader type
+ if (true_type == false_type || _is_type_compatible(true_type, false_type)) {
+ node_type = true_type;
+ } else if (_is_type_compatible(false_type, true_type)) {
+ node_type = false_type;
+ }
+
+ // TODO: Warn if types aren't compatible
+
+ } break;
+ // Assignment should never happen within an expression
+ case OperatorNode::OP_ASSIGN:
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_ASSIGN_BIT_XOR:
+ case OperatorNode::OP_INIT_ASSIGN: {
+
+ _set_error("Assignment inside expression is not allowed (parser bug?).", op->line);
+ return DataType();
+
+ } break;
+ case OperatorNode::OP_INDEX_NAMED: {
+ if (op->arguments.size() != 2) {
+ _set_error("Parser bug: named index with invalid arguments.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+ if (op->arguments[1]->type != Node::TYPE_IDENTIFIER) {
+ _set_error("Parser bug: named index without identifier argument.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ IdentifierNode *member_id = static_cast<IdentifierNode *>(op->arguments[1]);
+
+ if (base_type.has_type) {
+ if (check_types && base_type.kind == DataType::BUILTIN) {
+ // Variant type, just test if it's possible
+ DataType result;
+ switch (base_type.builtin_type) {
+ case Variant::NIL:
+ case Variant::DICTIONARY: {
+ result.has_type = false;
+ } break;
+ default: {
+ Variant::CallError err;
+ Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ bool valid = false;
+ Variant res = temp.get(member_id->name.operator String(), &valid);
+
+ if (valid) {
+ result = _type_from_variant(res);
+ } else if (check_types) {
+ _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" +
+ base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+ } break;
+ }
+ result.is_constant = false;
+ node_type = result;
+ } else {
+ node_type = _reduce_identifier_type(&base_type, member_id->name, op->line);
+ }
+ } else {
+ _mark_line_as_unsafe(op->line);
+ }
+ if (error_set) {
+ return DataType();
+ }
+ } break;
+ case OperatorNode::OP_INDEX: {
+
+ if (op->arguments[1]->type == Node::TYPE_CONSTANT) {
+ ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
+ if (cn->value.get_type() == Variant::STRING) {
+ // Treat this as named indexing
+
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->name = cn->value.operator StringName();
+
+ op->op = OperatorNode::OP_INDEX_NAMED;
+ op->arguments.write[1] = id;
+
+ return _reduce_node_type(op);
+ }
+ }
+
+ DataType base_type = _reduce_node_type(op->arguments[0]);
+ DataType index_type = _reduce_node_type(op->arguments[1]);
+
+ if (!base_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ if (check_types && index_type.has_type) {
+ if (base_type.kind == DataType::BUILTIN) {
+ // Check if indexing is valid
+ bool error = index_type.kind != DataType::BUILTIN;
+ if (!error) {
+ switch (base_type.builtin_type) {
+ // Expect int or real as index
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_COLOR_ARRAY:
+ case Variant::POOL_INT_ARRAY:
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::POOL_VECTOR3_ARRAY:
+ case Variant::ARRAY:
+ case Variant::STRING: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL;
+ } break;
+ // Expect String only
+ case Variant::RECT2:
+ case Variant::PLANE:
+ case Variant::QUAT:
+ case Variant::AABB:
+ case Variant::OBJECT: {
+ error = index_type.builtin_type != Variant::STRING;
+ } break;
+ // Expect String or number
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::TRANSFORM2D:
+ case Variant::BASIS:
+ case Variant::TRANSFORM: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::REAL &&
+ index_type.builtin_type != Variant::STRING;
+ } break;
+ // Expect String or int
+ case Variant::COLOR: {
+ error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
+ } break;
+ }
+ }
+ if (error) {
+ _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+
+ if (op->arguments[1]->type == GDScriptParser::Node::TYPE_CONSTANT) {
+ ConstantNode *cn = static_cast<ConstantNode *>(op->arguments[1]);
+ // Index is a constant, just try it if possible
+ switch (base_type.builtin_type) {
+ // Arrays/string have variable indexing, can't test directly
+ case Variant::STRING:
+ case Variant::ARRAY:
+ case Variant::DICTIONARY:
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_COLOR_ARRAY:
+ case Variant::POOL_INT_ARRAY:
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::POOL_VECTOR3_ARRAY: {
+ break;
+ }
+ default: {
+ Variant::CallError err;
+ Variant temp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ bool valid = false;
+ Variant res = temp.get(cn->value, &valid);
+
+ if (valid) {
+ node_type = _type_from_variant(res);
+ node_type.is_constant = false;
+ } else if (check_types) {
+ _set_error("Can't get index '" + String(cn->value) + "' on base '" +
+ base_type.to_string() + "'.",
+ op->line);
+ return DataType();
+ }
+ } break;
+ }
+ } else {
+ _mark_line_as_unsafe(op->line);
+ }
+ } else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
+ _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line);
+ return DataType();
+ }
+ }
+ if (check_types && !node_type.has_type) {
+ // Can infer indexing type for some variant types
+ DataType result;
+ result.has_type = true;
+ result.kind = DataType::BUILTIN;
+ switch (base_type.builtin_type) {
+ // Can't index at all
+ case Variant::NIL:
+ case Variant::BOOL:
+ case Variant::INT:
+ case Variant::REAL:
+ case Variant::NODE_PATH:
+ case Variant::_RID: {
+ _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line);
+ return DataType();
+ } break;
+ // Return int
+ case Variant::POOL_BYTE_ARRAY:
+ case Variant::POOL_INT_ARRAY: {
+ result.builtin_type = Variant::INT;
+ } break;
+ // Return real
+ case Variant::POOL_REAL_ARRAY:
+ case Variant::VECTOR2:
+ case Variant::VECTOR3:
+ case Variant::QUAT: {
+ result.builtin_type = Variant::REAL;
+ } break;
+ // Return color
+ case Variant::POOL_COLOR_ARRAY: {
+ result.builtin_type = Variant::COLOR;
+ } break;
+ // Return string
+ case Variant::POOL_STRING_ARRAY:
+ case Variant::STRING: {
+ result.builtin_type = Variant::STRING;
+ } break;
+ // Return Vector2
+ case Variant::POOL_VECTOR2_ARRAY:
+ case Variant::TRANSFORM2D:
+ case Variant::RECT2: {
+ result.builtin_type = Variant::VECTOR2;
+ } break;
+ // Return Vector3
+ case Variant::POOL_VECTOR3_ARRAY:
+ case Variant::AABB:
+ case Variant::BASIS: {
+ result.builtin_type = Variant::VECTOR3;
+ } break;
+ // Depends on the index
+ case Variant::TRANSFORM:
+ case Variant::PLANE:
+ case Variant::COLOR:
+ default: {
+ result.has_type = false;
+ } break;
+ }
+ node_type = result;
+ }
+ } break;
+ default: {
+ _set_error("Parser bug: unhandled operation.", op->line);
+ ERR_FAIL_V(DataType());
+ }
+ }
+ } break;
+ }
+
+ p_node->set_datatype(_resolve_type(node_type, p_node->line));
+ return node_type;
+}
+
+bool GDScriptParser::_get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const {
+
+ r_static = false;
+ r_default_arg_count = 0;
+
+ DataType original_type = p_base_type;
+ ClassNode *base = NULL;
+ FunctionNode *callee = NULL;
+
+ if (p_base_type.kind == DataType::CLASS) {
+ base = p_base_type.class_type;
+ }
+
+ // Look up the current file (parse tree)
+ while (!callee && base) {
+ for (int i = 0; i < base->static_functions.size(); i++) {
+ FunctionNode *func = base->static_functions[i];
+ if (p_function == func->name) {
+ r_static = true;
+ callee = func;
+ break;
+ }
+ }
+ if (!callee && !p_base_type.is_meta_type) {
+ for (int i = 0; i < base->functions.size(); i++) {
+ FunctionNode *func = base->functions[i];
+ if (p_function == func->name) {
+ callee = func;
+ break;
+ }
+ }
+ }
+ p_base_type = base->base_type;
+ if (p_base_type.kind == DataType::CLASS) {
+ base = p_base_type.class_type;
+ } else {
+ break;
+ }
+ }
+
+ if (callee) {
+ r_return_type = callee->get_datatype();
+ for (int i = 0; i < callee->argument_types.size(); i++) {
+ r_arg_types.push_back(callee->argument_types[i]);
+ }
+ r_default_arg_count = callee->default_values.size();
+ return true;
+ }
+
+ // Nothing in current file, check parent script
+ Ref<GDScript> base_gdscript;
+ Ref<Script> base_script;
+ StringName native;
+ if (p_base_type.kind == DataType::GDSCRIPT) {
+ base_gdscript = p_base_type.script_type;
+ } else if (p_base_type.kind == DataType::SCRIPT) {
+ base_script = p_base_type.script_type;
+ } else if (p_base_type.kind == DataType::NATIVE) {
+ native = p_base_type.native_type;
+ }
+
+ while (base_gdscript.is_valid()) {
+ native = base_gdscript->get_instance_base_type();
+
+ Map<StringName, GDScriptFunction *> funcs = base_gdscript->get_member_functions();
+
+ if (funcs.has(p_function)) {
+ GDScriptFunction *f = funcs[p_function];
+ r_static = f->is_static();
+ r_default_arg_count = f->get_default_argument_count();
+ r_return_type = _type_from_gdtype(f->get_return_type());
+ for (int i = 0; i < f->get_argument_count(); i++) {
+ r_arg_types.push_back(_type_from_gdtype(f->get_argument_type(i)));
+ }
+ return true;
+ }
+
+ base_gdscript = base_gdscript->get_base_script();
+ }
+
+ while (base_script.is_valid()) {
+ native = base_script->get_instance_base_type();
+ MethodInfo mi = base_script->get_method_info(p_function);
+
+ if (!(mi == MethodInfo())) {
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_default_arg_count = mi.default_arguments.size();
+ for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
+ r_arg_types.push_back(_type_from_property(E->get()));
+ }
+ return true;
+ }
+ base_script = base_script->get_base_script();
+ }
+
+#ifdef DEBUG_METHODS_ENABLED
+
+ // Only native remains
+ if (!ClassDB::class_exists(native)) {
+ native = "_" + native.operator String();
+ }
+ if (!ClassDB::class_exists(native)) {
+ if (!check_types) return false;
+ ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
+ ERR_FAIL_V(false);
+ }
+
+ MethodBind *method = ClassDB::get_method(native, p_function);
+
+ if (!method) {
+ // Try virtual methods
+ List<MethodInfo> virtuals;
+ ClassDB::get_virtual_methods(native, &virtuals);
+
+ for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
+ const MethodInfo &mi = E->get();
+ if (mi.name == p_function) {
+ r_default_arg_count = mi.default_arguments.size();
+ for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
+ r_arg_types.push_back(_type_from_property(pi->get()));
+ }
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_vararg = mi.flags & METHOD_FLAG_VARARG;
+ return true;
+ }
+ }
+
+ // If the base is a script, it might be trying to access members of the Script class itself
+ if (original_type.is_meta_type && !(p_function == "new") && (original_type.kind == DataType::SCRIPT || original_type.kind == DataType::GDSCRIPT)) {
+ method = ClassDB::get_method(original_type.script_type->get_class_name(), p_function);
+
+ if (method) {
+ r_static = true;
+ } else {
+ // Try virtual methods of the script type
+ virtuals.clear();
+ ClassDB::get_virtual_methods(original_type.script_type->get_class_name(), &virtuals);
+ for (const List<MethodInfo>::Element *E = virtuals.front(); E; E = E->next()) {
+ const MethodInfo &mi = E->get();
+ if (mi.name == p_function) {
+ r_default_arg_count = mi.default_arguments.size();
+ for (const List<PropertyInfo>::Element *pi = mi.arguments.front(); pi; pi = pi->next()) {
+ r_arg_types.push_back(_type_from_property(pi->get()));
+ }
+ r_return_type = _type_from_property(mi.return_val, false);
+ r_static = true;
+ r_vararg = mi.flags & METHOD_FLAG_VARARG;
+ return true;
+ }
+ }
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ r_default_arg_count = method->get_default_argument_count();
+ r_return_type = _type_from_property(method->get_return_info(), false);
+ r_vararg = method->is_vararg();
+
+ for (int i = 0; i < method->get_argument_count(); i++) {
+ r_arg_types.push_back(_type_from_property(method->get_argument_info(i)));
+ }
+ return true;
+#else
+ return false;
+#endif
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const OperatorNode *p_call) {
+ if (p_call->arguments.size() < 1) {
+ _set_error("Parser bug: function call without enough arguments.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ DataType return_type;
+ List<DataType> arg_types;
+ int default_args_count = 0;
+ int arg_count = p_call->arguments.size();
+ String callee_name;
+ bool is_vararg = false;
+
+ switch (p_call->arguments[0]->type) {
+ case GDScriptParser::Node::TYPE_TYPE: {
+ // Built-in constructor, special case
+ TypeNode *tn = static_cast<TypeNode *>(p_call->arguments[0]);
+
+ Vector<DataType> par_types;
+ par_types.resize(p_call->arguments.size() - 1);
+ for (int i = 1; i < p_call->arguments.size(); i++) {
+ par_types.write[i - 1] = _reduce_node_type(p_call->arguments[i]);
+ }
+
+ if (error_set) return DataType();
+
+ bool match = false;
+ List<MethodInfo> constructors;
+ Variant::get_constructor_list(tn->vtype, &constructors);
+ PropertyInfo return_type;
+
+ for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) {
+ MethodInfo &mi = E->get();
+
+ if (p_call->arguments.size() - 1 < mi.arguments.size() - mi.default_arguments.size()) {
+ continue;
+ }
+ if (p_call->arguments.size() - 1 > mi.arguments.size()) {
+ continue;
+ }
+
+ bool types_match = true;
+ for (int i = 0; i < par_types.size(); i++) {
+ DataType arg_type;
+ if (mi.arguments[i].type != Variant::NIL) {
+ arg_type.has_type = true;
+ arg_type.kind = mi.arguments[i].type == Variant::OBJECT ? DataType::NATIVE : DataType::BUILTIN;
+ arg_type.builtin_type = mi.arguments[i].type;
+ arg_type.native_type = mi.arguments[i].class_name;
+ }
+
+ if (!_is_type_compatible(arg_type, par_types[i], true)) {
+ types_match = false;
+ break;
+ }
+ }
+
+ if (types_match) {
+ match = true;
+ return_type = mi.return_val;
+ break;
+ }
+ }
+
+ if (match) {
+ return _type_from_property(return_type, false);
+ } else if (check_types) {
+ String err = "No constructor of '";
+ err += Variant::get_type_name(tn->vtype);
+ err += "' matches the signature '";
+ err += Variant::get_type_name(tn->vtype) + "(";
+ for (int i = 0; i < par_types.size(); i++) {
+ if (i > 0) err += ", ";
+ err += par_types[i].to_string();
+ }
+ err += ")'.";
+ _set_error(err, p_call->line, p_call->column);
+ return DataType();
+ }
+ return DataType();
+ } break;
+ case GDScriptParser::Node::TYPE_BUILT_IN_FUNCTION: {
+ BuiltInFunctionNode *func = static_cast<BuiltInFunctionNode *>(p_call->arguments[0]);
+ MethodInfo mi = GDScriptFunctions::get_info(func->function);
+
+ return_type = _type_from_property(mi.return_val, false);
+
+ // Check arguments
+
+ is_vararg = mi.flags & METHOD_FLAG_VARARG;
+
+ default_args_count = mi.default_arguments.size();
+ callee_name = mi.name;
+ arg_count -= 1;
+
+ // Check each argument type
+ for (List<PropertyInfo>::Element *E = mi.arguments.front(); E; E = E->next()) {
+ arg_types.push_back(_type_from_property(E->get()));
+ }
+ } break;
+ default: {
+ if (p_call->op == OperatorNode::OP_CALL && p_call->arguments.size() < 2) {
+ _set_error("Parser bug: self method call without enough arguments.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ int arg_id = p_call->op == OperatorNode::OP_CALL ? 1 : 0;
+
+ if (p_call->arguments[arg_id]->type != Node::TYPE_IDENTIFIER) {
+ _set_error("Parser bug: invalid function call argument.", p_call->line);
+ ERR_FAIL_V(DataType());
+ }
+
+ IdentifierNode *func_id = static_cast<IdentifierNode *>(p_call->arguments[arg_id]);
+ callee_name = func_id->name;
+ arg_count -= 1 + arg_id;
+
+ DataType base_type;
+ if (p_call->op == OperatorNode::OP_PARENT_CALL) {
+ base_type = current_class->base_type;
+ } else {
+ base_type = _reduce_node_type(p_call->arguments[0]);
+ }
+
+ if (!base_type.has_type || (base_type.kind == DataType::BUILTIN && base_type.builtin_type == Variant::NIL)) {
+ _mark_line_as_unsafe(p_call->line);
+ return DataType();
+ }
+
+ if (base_type.kind == DataType::BUILTIN) {
+ Variant::CallError err;
+ Variant tmp = Variant::construct(base_type.builtin_type, NULL, 0, err);
+
+ if (check_types) {
+ if (!tmp.has_method(callee_name)) {
+ _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line);
+ return DataType();
+ }
+
+ default_args_count = Variant::get_method_default_arguments(base_type.builtin_type, callee_name).size();
+ const Vector<Variant::Type> &var_arg_types = Variant::get_method_argument_types(base_type.builtin_type, callee_name);
+
+ for (int i = 0; i < var_arg_types.size(); i++) {
+ DataType argtype;
+ if (var_arg_types[i] != Variant::NIL) {
+ argtype.has_type = true;
+ argtype.kind = DataType::BUILTIN;
+ argtype.builtin_type = var_arg_types[i];
+ }
+ arg_types.push_back(argtype);
+ }
+ }
+
+ return_type.has_type = true;
+ return_type.kind = DataType::BUILTIN;
+ return_type.builtin_type = Variant::get_method_return_type(base_type.builtin_type, callee_name);
+ break;
+ }
+
+ DataType original_type = base_type;
+ bool is_initializer = callee_name == "new";
+ bool is_static = false;
+ bool valid = false;
+
+ if (is_initializer && original_type.is_meta_type) {
+ // Try to check it as initializer
+ base_type = original_type;
+ callee_name = "_init";
+ base_type.is_meta_type = false;
+
+ valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
+ default_args_count, is_static, is_vararg);
+
+ if (valid) {
+ return_type = original_type;
+ return_type.is_meta_type = false;
+ }
+ }
+
+ if (!valid) {
+ base_type = original_type;
+ return_type = DataType();
+ valid = _get_function_signature(base_type, callee_name, return_type, arg_types,
+ default_args_count, is_static, is_vararg);
+ }
+
+ if (!valid) {
+#ifdef DEBUG_ENABLED
+ if (p_call->arguments[0]->type == Node::TYPE_SELF) {
+ _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
+ return DataType();
+ }
+ _mark_line_as_unsafe(p_call->line);
+#endif
+ return DataType();
+ }
+
+#ifdef DEBUG_ENABLED
+ if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
+ if (current_function && current_function->_static && p_call->arguments[0]->type == Node::TYPE_SELF) {
+ _set_error("Can't call non-static function from a static function.", p_call->line);
+ return DataType();
+ }
+ }
+
+ if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
+ _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line);
+ return DataType();
+ }
+#endif
+ } break;
+ }
+
+ if (!check_types) {
+ return return_type;
+ }
+
+ if (arg_count < arg_types.size() - default_args_count) {
+ _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
+ return return_type;
+ }
+ if (!is_vararg && arg_count > arg_types.size()) {
+ _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
+ return return_type;
+ }
+
+ int arg_diff = p_call->arguments.size() - arg_count;
+ for (int i = arg_diff; i < p_call->arguments.size(); i++) {
+ DataType par_type = _reduce_node_type(p_call->arguments[i]);
+
+ if ((i - arg_diff) >= arg_types.size()) {
+ continue;
+ }
+
+ if (!par_type.has_type) {
+ _mark_line_as_unsafe(p_call->line);
+ } else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
+ // Supertypes are acceptable for dynamic compliance
+ if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
+ _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
+ par_type.to_string() + ") doesn't match the function argument's type (" +
+ arg_types[i - arg_diff].to_string() + ").",
+ p_call->line);
+ return DataType();
+ } else {
+ _mark_line_as_unsafe(p_call->line);
+ }
+ }
+ }
+
+ return return_type;
+}
+
+bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const {
+ DataType base_type = p_base_type;
+
+ // Check classes in current file
+ ClassNode *base = NULL;
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+
+ while (base) {
+ if (base->constant_expressions.has(p_member)) {
+ r_member_type = base->constant_expressions[p_member].expression->get_datatype();
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ for (int i = 0; i < base->variables.size(); i++) {
+ ClassNode::Member m = base->variables[i];
+ if (m.identifier == p_member) {
+ r_member_type = m.data_type;
+ return true;
+ }
+ }
+ } else {
+ for (int i = 0; i < base->subclasses.size(); i++) {
+ ClassNode *c = base->subclasses[i];
+ if (c->name == p_member) {
+ DataType class_type;
+ class_type.has_type = true;
+ class_type.is_constant = true;
+ class_type.is_meta_type = true;
+ class_type.kind = DataType::CLASS;
+ class_type.class_type = c;
+ r_member_type = class_type;
+ return true;
+ }
+ }
+ }
+
+ base_type = base->base_type;
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ } else {
+ break;
+ }
+ }
+
+ Ref<GDScript> gds;
+ if (base_type.kind == DataType::GDSCRIPT) {
+ gds = base_type.script_type;
+ }
+
+ Ref<Script> scr;
+ if (base_type.kind == DataType::SCRIPT) {
+ scr = base_type.script_type;
+ }
+
+ StringName native;
+ if (base_type.kind == DataType::NATIVE) {
+ native = base_type.native_type;
+ }
+
+ // Check GDScripts
+ while (gds.is_valid()) {
+ if (gds->get_constants().has(p_member)) {
+ Variant c = gds->get_constants()[p_member];
+ r_member_type = _type_from_variant(c);
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ if (gds->get_members().has(p_member)) {
+ r_member_type = _type_from_gdtype(gds->get_member_type(p_member));
+ return true;
+ }
+ }
+
+ native = gds->get_instance_base_type();
+ if (gds->get_base_script().is_valid()) {
+ gds = gds->get_base_script();
+ scr = gds->get_base_script();
+ bool is_meta = base_type.is_meta_type;
+ base_type = _type_from_variant(scr.operator Variant());
+ base_type.is_meta_type = is_meta;
+ } else {
+ break;
+ }
+ }
+
+ // Check other script types
+ while (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
+ if (constants.has(p_member)) {
+ r_member_type = _type_from_variant(constants[p_member]);
+ return true;
+ }
+
+ List<PropertyInfo> properties;
+ scr->get_script_property_list(&properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ r_member_type = _type_from_property(E->get());
+ return true;
+ }
+ }
+
+ base_type = _type_from_variant(scr.operator Variant());
+ native = scr->get_instance_base_type();
+ scr = scr->get_base_script();
+ }
+
+ // Check ClassDB
+ if (!ClassDB::class_exists(native)) {
+ native = "_" + native.operator String();
+ }
+ if (!ClassDB::class_exists(native)) {
+ if (!check_types) return false;
+ ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
+ ERR_FAIL_V(false);
+ }
+
+ bool valid = false;
+ ClassDB::get_integer_constant(native, p_member, &valid);
+ if (valid) {
+ DataType ct;
+ ct.has_type = true;
+ ct.is_constant = true;
+ ct.kind = DataType::BUILTIN;
+ ct.builtin_type = Variant::INT;
+ r_member_type = ct;
+ return true;
+ }
+
+ if (!base_type.is_meta_type) {
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(native, &properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ // Check if a getter exists
+ StringName getter_name = ClassDB::get_property_getter(native, p_member);
+ if (getter_name != StringName()) {
+ // Use the getter return type
+#ifdef DEBUG_METHODS_ENABLED
+ MethodBind *getter_method = ClassDB::get_method(native, getter_name);
+ if (getter_method) {
+ r_member_type = _type_from_property(getter_method->get_return_info());
+ } else {
+ r_member_type = DataType();
+ }
+#else
+ r_member_type = DataType();
+#endif
+ } else {
+ r_member_type = _type_from_property(E->get());
+ }
+ return true;
+ }
+ }
+ }
+
+ // 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_base_type.kind == DataType::SCRIPT || p_base_type.kind == DataType::GDSCRIPT)) {
+ native = p_base_type.script_type->get_class_name();
+ ClassDB::get_integer_constant(native, p_member, &valid);
+ if (valid) {
+ DataType ct;
+ ct.has_type = true;
+ ct.is_constant = true;
+ ct.kind = DataType::BUILTIN;
+ ct.builtin_type = Variant::INT;
+ r_member_type = ct;
+ return true;
+ }
+
+ List<PropertyInfo> properties;
+ ClassDB::get_property_list(native, &properties);
+ for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name == p_member) {
+ // Check if a getter exists
+ StringName getter_name = ClassDB::get_property_getter(native, p_member);
+ if (getter_name != StringName()) {
+ // Use the getter return type
+#ifdef DEBUG_METHODS_ENABLED
+ MethodBind *getter_method = ClassDB::get_method(native, getter_name);
+ if (getter_method) {
+ r_member_type = _type_from_property(getter_method->get_return_info());
+ } else {
+ r_member_type = DataType();
+ }
+#else
+ r_member_type = DataType();
+#endif
+ } else {
+ r_member_type = _type_from_property(E->get());
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType *p_base_type, const StringName &p_identifier, int p_line) {
+
+ if (p_base_type && !p_base_type->has_type) {
+ return DataType();
+ }
+
+ DataType base_type;
+
+ // Check classes in current file
+ ClassNode *base = NULL;
+ if (!p_base_type) {
+ base = current_class;
+ base_type.has_type = true;
+ base_type.is_constant = true;
+ base_type.kind = DataType::CLASS;
+ base_type.class_type = base;
+ } else {
+ base_type = DataType(*p_base_type);
+ if (base_type.kind == DataType::CLASS) {
+ base = base_type.class_type;
+ }
+ }
+
+ DataType member_type;
+
+ if (_get_member_type(base_type, p_identifier, member_type)) {
+ return member_type;
+ }
+
+ if (!p_base_type) {
+ // Possibly this is a global, check before failing
+
+ if (ClassDB::class_exists(p_identifier) || ClassDB::class_exists("_" + p_identifier.operator String())) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ if (Engine::get_singleton()->has_singleton(p_identifier) || Engine::get_singleton()->has_singleton("_" + p_identifier.operator String())) {
+ result.is_meta_type = false;
+ }
+ result.kind = DataType::NATIVE;
+ result.native_type = p_identifier;
+ return result;
+ }
+
+ ClassNode *outer_class = current_class;
+ while (outer_class) {
+ if (outer_class->name == p_identifier) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class;
+ return result;
+ }
+ if (outer_class->constant_expressions.has(p_identifier)) {
+ return outer_class->constant_expressions[p_identifier].type;
+ }
+ for (int i = 0; i < outer_class->subclasses.size(); i++) {
+ if (outer_class->subclasses[i] == current_class) {
+ continue;
+ }
+ if (outer_class->subclasses[i]->name == p_identifier) {
+ DataType result;
+ result.has_type = true;
+ result.is_constant = true;
+ result.is_meta_type = true;
+ result.kind = DataType::CLASS;
+ result.class_type = outer_class->subclasses[i];
+ return result;
+ }
+ }
+ outer_class = outer_class->owner;
+ }
+
+ if (ScriptServer::is_global_class(p_identifier)) {
+ Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier));
+ if (scr.is_valid()) {
+ DataType result;
+ result.has_type = true;
+ result.script_type = scr;
+ result.is_meta_type = true;
+ Ref<GDScript> gds = scr;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic inheritance).");
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ return result;
+ }
+ _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded.");
+ return DataType();
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(p_identifier)) {
+ int idx = GDScriptLanguage::get_singleton()->get_global_map()[p_identifier];
+ Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx];
+ return _type_from_variant(g);
+ }
+
+ if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier)) {
+ Variant g = GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier];
+ return _type_from_variant(g);
+ }
+
+ // Non-tool singletons aren't loaded, check project settings
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == p_identifier) {
+ String script = ProjectSettings::get_singleton()->get(s);
+ if (script.begins_with("*")) {
+ script = script.right(1);
+ }
+ if (!script.begins_with("res://")) {
+ script = "res://" + script;
+ }
+ Ref<Script> singleton = ResourceLoader::load(script);
+ if (singleton.is_valid()) {
+ DataType result;
+ result.has_type = true;
+ result.script_type = singleton;
+
+ Ref<GDScript> gds = singleton;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ } else {
+ result.kind = DataType::SCRIPT;
+ }
+ }
+ }
+ }
+
+ // This means looking in the current class, which type is always known
+ _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
+ }
+
+ _mark_line_as_unsafe(p_line);
+ return DataType();
+}
+
+void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
+
+ _mark_line_as_safe(p_class->line);
+
+ // Constants
+ for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+ ClassNode::Constant &c = E->get();
+ _mark_line_as_safe(c.expression->line);
+ DataType cont = _resolve_type(c.type, c.expression->line);
+ DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
+
+ if (!_is_type_compatible(cont, expr)) {
+ _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").",
+ c.expression->line);
+ return;
+ }
+
+ expr.is_constant = true;
+ c.type = expr;
+ c.expression->set_datatype(expr);
+ }
+
+ // Function declarations
+ for (int i = 0; i < p_class->static_functions.size(); i++) {
+ _check_function_types(p_class->static_functions[i]);
+ if (error_set) return;
+ }
+
+ for (int i = 0; i < p_class->functions.size(); i++) {
+ _check_function_types(p_class->functions[i]);
+ if (error_set) return;
+ }
+
+ // Class variables
+ for (int i = 0; i < p_class->variables.size(); i++) {
+ ClassNode::Member &v = p_class->variables.write[i];
+
+ DataType tmp;
+ if (_get_member_type(p_class->base_type, v.identifier, tmp)) {
+ _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line);
+ return;
+ }
+
+ _mark_line_as_safe(v.line);
+ v.data_type = _resolve_type(v.data_type, v.line);
+
+ if (v.expression) {
+ DataType expr_type = _reduce_node_type(v.expression);
+
+ if (!_is_type_compatible(v.data_type, expr_type)) {
+ // Try supertype test
+ if (_is_type_compatible(expr_type, v.data_type)) {
+ _mark_line_as_unsafe(v.line);
+ } else {
+ // Try with implicit conversion
+ if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
+ _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
+ v.data_type.to_string() + ").",
+ v.line);
+ return;
+ }
+
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = v.line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = v.line;
+ tgt_type->value = (int)v.data_type.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = v.line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(v.expression);
+ convert_call->arguments.push_back(tgt_type);
+
+ v.expression = convert_call;
+ v.initial_assignment->arguments.write[1] = convert_call;
+ }
+ }
+
+ if (v.data_type.infer_type) {
+ if (!expr_type.has_type) {
+ _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line);
+ return;
+ }
+ v.data_type = expr_type;
+ v.data_type.is_constant = false;
+ }
+ } else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) {
+ // Create default value based on the type
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->line = v.line;
+ id->name = v.identifier;
+
+ ConstantNode *init = alloc_node<ConstantNode>();
+ init->line = v.line;
+ Variant::CallError err;
+ init->value = Variant::construct(v.data_type.builtin_type, NULL, 0, err);
+
+ OperatorNode *op = alloc_node<OperatorNode>();
+ op->line = v.line;
+ op->op = OperatorNode::OP_INIT_ASSIGN;
+ op->arguments.push_back(id);
+ op->arguments.push_back(init);
+
+ p_class->initializer->statements.push_front(op);
+ v.initial_assignment = op;
+#ifdef DEBUG_ENABLED
+ NewLineNode *nl = alloc_node<NewLineNode>();
+ nl->line = v.line - 1;
+ p_class->initializer->statements.push_front(nl);
+#endif
+ }
+
+ // Check export hint
+ if (v.data_type.has_type && v._export.type != Variant::NIL) {
+ DataType export_type = _type_from_property(v._export);
+ if (!_is_type_compatible(v.data_type, export_type, true)) {
+ _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
+ v.data_type.to_string() + ").",
+ v.line);
+ return;
+ }
+ }
+
+ // Setter and getter
+ if (v.setter == StringName() && v.getter == StringName()) continue;
+
+ bool found_getter = false;
+ bool found_setter = false;
+ for (int j = 0; j < p_class->functions.size(); j++) {
+ if (v.setter == p_class->functions[j]->name) {
+ found_setter = true;
+ FunctionNode *setter = p_class->functions[j];
+
+ if (setter->arguments.size() != 1) {
+ _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name +
+ "()' definition at line " + itos(setter->line) + ".",
+ v.line);
+ return;
+ }
+ if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
+ _set_error("Setter argument type (" + setter->argument_types[0].to_string() +
+ ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" +
+ setter->name + "()' definition at line " + itos(setter->line) + ".",
+ v.line);
+ return;
+ }
+ continue;
+ }
+ if (v.getter == p_class->functions[j]->name) {
+ found_getter = true;
+ FunctionNode *getter = p_class->functions[j];
+
+ if (getter->arguments.size() != 0) {
+ _set_error("Getter function can't receive arguments. See '" + getter->name +
+ "()' definition at line " + itos(getter->line) + ".",
+ v.line);
+ return;
+ }
+ if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
+ _set_error("Getter return type (" + getter->get_datatype().to_string() +
+ ") doesn't match the variable's type (" + v.data_type.to_string() +
+ "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".",
+ v.line);
+ return;
+ }
+ }
+ if (found_getter && found_setter) break;
+ }
+
+ if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue;
+
+ // Check for static functions
+ for (int j = 0; j < p_class->static_functions.size(); j++) {
+ if (v.setter == p_class->static_functions[j]->name) {
+ FunctionNode *setter = p_class->static_functions[j];
+ _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line);
+ return;
+ }
+ if (v.getter == p_class->static_functions[j]->name) {
+ FunctionNode *getter = p_class->static_functions[j];
+ _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line);
+ return;
+ }
+ }
+
+ if (!found_setter && v.setter != StringName()) {
+ _set_error("Setter function is not defined.", v.line);
+ return;
+ }
+
+ if (!found_getter && v.getter != StringName()) {
+ _set_error("Getter function is not defined.", v.line);
+ return;
+ }
+ }
+
+ // Inner classes
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ current_class = p_class->subclasses[i];
+ _check_class_level_types(current_class);
+ if (error_set) return;
+ current_class = p_class;
+ }
+}
+
+void GDScriptParser::_check_function_types(FunctionNode *p_function) {
+
+ p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
+
+ // Arguments
+ int defaults_ofs = p_function->arguments.size() - p_function->default_values.size();
+ for (int i = 0; i < p_function->arguments.size(); i++) {
+ if (i < defaults_ofs) {
+ p_function->argument_types.write[i] = _resolve_type(p_function->argument_types[i], p_function->line);
+ } else {
+ if (p_function->default_values[i - defaults_ofs]->type != Node::TYPE_OPERATOR) {
+ _set_error("Parser bug: invalid argument default value.", p_function->line, p_function->column);
+ return;
+ }
+
+ OperatorNode *op = static_cast<OperatorNode *>(p_function->default_values[i - defaults_ofs]);
+
+ if (op->op != OperatorNode::OP_ASSIGN || op->arguments.size() != 2) {
+ _set_error("Parser bug: invalid argument default value operation.", p_function->line);
+ return;
+ }
+
+ DataType def_type = _reduce_node_type(op->arguments[1]);
+
+ if (p_function->argument_types[i].infer_type) {
+ def_type.is_constant = false;
+ p_function->argument_types.write[i] = def_type;
+ } else {
+ p_function->return_type = _resolve_type(p_function->return_type, p_function->line);
+
+ if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
+ String arg_name = p_function->arguments[i];
+ _set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
+ arg_name + "' (" + p_function->arguments[i] + ")",
+ p_function->line);
+ }
+ }
+ }
+ }
+
+ if (!(p_function->name == "_init")) {
+ // Signature for the initializer may vary
+#ifdef DEBUG_ENABLED
+ DataType return_type;
+ List<DataType> arg_types;
+ int default_arg_count = 0;
+ bool _static = false;
+ bool vararg = false;
+
+ DataType base_type = current_class->base_type;
+ if (_get_function_signature(base_type, p_function->name, return_type, arg_types, default_arg_count, _static, vararg)) {
+ bool valid = _static == p_function->_static;
+ valid = valid && return_type == p_function->return_type;
+ int argsize_diff = p_function->arguments.size() - arg_types.size();
+ valid = valid && argsize_diff >= 0;
+ valid = valid && p_function->default_values.size() >= default_arg_count + argsize_diff;
+ int i = 0;
+ for (List<DataType>::Element *E = arg_types.front(); valid && E; E = E->next()) {
+ valid = valid && E->get() == p_function->argument_types[i++];
+ }
+
+ if (!valid) {
+ String parent_signature = return_type.has_type ? return_type.to_string() : "Variant";
+ if (parent_signature == "null") {
+ parent_signature = "void";
+ }
+ parent_signature += " " + p_function->name + "(";
+ if (arg_types.size()) {
+ int i = 0;
+ for (List<DataType>::Element *E = arg_types.front(); E; E = E->next()) {
+ if (E != arg_types.front()) {
+ parent_signature += ", ";
+ }
+ String arg = E->get().to_string();
+ if (arg == "null" || arg == "var") {
+ arg = "Variant";
+ }
+ parent_signature += arg;
+ if (i == arg_types.size() - default_arg_count) {
+ parent_signature += "=default";
+ }
+
+ i++;
+ }
+ }
+ parent_signature += ")";
+ _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line);
+ return;
+ }
+ }
+#endif // DEBUG_ENABLED
+ } else {
+ if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
+ _set_error("Constructor cannot return a value.", p_function->line);
+ return;
+ }
+ }
+
+ if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
+ if (!p_function->body->has_return) {
+ _set_error("Non-void function must return a value in all possible paths.", p_function->line);
+ return;
+ }
+ }
+
+ if (p_function->has_yield) {
+ // yield() will make the function return a GDScriptFunctionState, so the type is ambiguous
+ p_function->return_type.has_type = false;
+ }
+}
+
+void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
+
+ // Function blocks
+ for (int i = 0; i < p_class->static_functions.size(); i++) {
+ current_function = p_class->static_functions[i];
+ current_block = current_function->body;
+ _mark_line_as_safe(current_function->line);
+ _check_block_types(current_block);
+ current_block = NULL;
+ current_function = NULL;
+ if (error_set) return;
+ }
+
+ for (int i = 0; i < p_class->functions.size(); i++) {
+ current_function = p_class->functions[i];
+ current_block = current_function->body;
+ _mark_line_as_safe(current_function->line);
+ _check_block_types(current_block);
+ current_block = NULL;
+ current_function = NULL;
+ if (error_set) return;
+ }
+
+ // Inner classes
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ current_class = p_class->subclasses[i];
+ _check_class_blocks_types(current_class);
+ if (error_set) return;
+ current_class = p_class;
+ }
+}
+
+void GDScriptParser::_check_block_types(BlockNode *p_block) {
+
+ Node *last_var_assign = NULL;
+
+ // Check each statement
+ for (List<Node *>::Element *E = p_block->statements.front(); E; E = E->next()) {
+ Node *statement = E->get();
+ switch (statement->type) {
+ case Node::TYPE_NEWLINE:
+ case Node::TYPE_BREAKPOINT:
+ case Node::TYPE_ASSERT: {
+ // Nothing to do
+ } break;
+ case Node::TYPE_LOCAL_VAR: {
+ LocalVarNode *lv = static_cast<LocalVarNode *>(statement);
+ lv->datatype = _resolve_type(lv->datatype, lv->line);
+ _mark_line_as_safe(lv->line);
+
+ if (lv->assign) {
+ DataType assign_type = _reduce_node_type(lv->assign);
+ if (!_is_type_compatible(lv->datatype, assign_type)) {
+ // Try supertype test
+ if (_is_type_compatible(assign_type, lv->datatype)) {
+ _mark_line_as_unsafe(lv->line);
+ } else {
+ // Try implict conversion
+ if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
+ _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
+ lv->datatype.to_string() + ").",
+ lv->line);
+ return;
+ }
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = lv->line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = lv->line;
+ tgt_type->value = (int)lv->datatype.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = lv->line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(lv->assign);
+ convert_call->arguments.push_back(tgt_type);
+
+ lv->assign = convert_call;
+ lv->assign_op->arguments.write[1] = convert_call;
+ }
+ }
+ if (lv->datatype.infer_type) {
+ if (!assign_type.has_type) {
+ _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line);
+ return;
+ }
+ lv->datatype = assign_type;
+ lv->datatype.is_constant = false;
+ }
+ if (lv->datatype.has_type && !assign_type.has_type) {
+ _mark_line_as_unsafe(lv->line);
+ }
+ }
+ last_var_assign = lv->assign;
+
+ // TODO: Make a warning
+ /*
+ if (lv->assignments == 0) {
+ _set_error("Variable '" + String(lv->name) + "' is never assigned.", lv->line);
+ return;
+ }
+ */
+ } break;
+ case Node::TYPE_OPERATOR: {
+ OperatorNode *op = static_cast<OperatorNode *>(statement);
+
+ switch (op->op) {
+ case OperatorNode::OP_ASSIGN:
+ case OperatorNode::OP_ASSIGN_ADD:
+ case OperatorNode::OP_ASSIGN_SUB:
+ case OperatorNode::OP_ASSIGN_MUL:
+ case OperatorNode::OP_ASSIGN_DIV:
+ case OperatorNode::OP_ASSIGN_MOD:
+ case OperatorNode::OP_ASSIGN_SHIFT_LEFT:
+ case OperatorNode::OP_ASSIGN_SHIFT_RIGHT:
+ case OperatorNode::OP_ASSIGN_BIT_AND:
+ case OperatorNode::OP_ASSIGN_BIT_OR:
+ case OperatorNode::OP_ASSIGN_BIT_XOR: {
+ if (op->arguments.size() < 2) {
+ _set_error("Parser bug: operation without enough arguments.", op->line, op->column);
+ return;
+ }
+
+ if (op->arguments[1] == last_var_assign) {
+ // Assignment was already checked
+ break;
+ }
+
+ _mark_line_as_safe(op->line);
+
+ DataType lh_type = _reduce_node_type(op->arguments[0]);
+
+ if (error_set) {
+ return;
+ }
+
+ if (!lh_type.has_type) {
+ if (op->arguments[0]->type == Node::TYPE_OPERATOR) {
+ _mark_line_as_unsafe(op->line);
+ }
+ } else if (lh_type.is_constant) {
+ _set_error("Cannot assign a new value to a constant.", op->line);
+ return;
+ }
+
+ DataType rh_type;
+ if (op->op != OperatorNode::OP_ASSIGN) {
+ // Validate operation
+ DataType arg_type = _reduce_node_type(op->arguments[1]);
+ if (!arg_type.has_type) {
+ _mark_line_as_unsafe(op->line);
+ break;
+ }
+
+ Variant::Operator oper = _get_variant_operation(op->op);
+ bool valid = false;
+ rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
+
+ if (!valid) {
+ _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() +
+ "') to assignment operator '" + Variant::get_operator_name(oper) + "'.",
+ op->line);
+ return;
+ }
+ } else {
+ rh_type = _reduce_node_type(op->arguments[1]);
+ }
+
+ if (!_is_type_compatible(lh_type, rh_type)) {
+ // Try supertype test
+ if (_is_type_compatible(rh_type, lh_type)) {
+ _mark_line_as_unsafe(op->line);
+ } else {
+ // Try implict conversion
+ if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
+ _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
+ lh_type.to_string() + ").",
+ op->line);
+ return;
+ }
+ // Replace assigment with implict conversion
+ BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
+ convert->line = op->line;
+ convert->function = GDScriptFunctions::TYPE_CONVERT;
+
+ ConstantNode *tgt_type = alloc_node<ConstantNode>();
+ tgt_type->line = op->line;
+ tgt_type->value = (int)lh_type.builtin_type;
+
+ OperatorNode *convert_call = alloc_node<OperatorNode>();
+ convert_call->line = op->line;
+ convert_call->op = OperatorNode::OP_CALL;
+ convert_call->arguments.push_back(convert);
+ convert_call->arguments.push_back(op->arguments[1]);
+ convert_call->arguments.push_back(tgt_type);
+
+ op->arguments.write[1] = convert_call;
+ }
+ }
+ if (!rh_type.has_type && (op->op != OperatorNode::OP_ASSIGN || lh_type.has_type || op->arguments[0]->type == Node::TYPE_OPERATOR)) {
+ _mark_line_as_unsafe(op->line);
+ }
+ } break;
+ case OperatorNode::OP_CALL:
+ case OperatorNode::OP_PARENT_CALL: {
+ _mark_line_as_safe(op->line);
+ _reduce_function_call_type(op);
+ if (error_set) return;
+ } break;
+ default: {
+ _mark_line_as_safe(op->line);
+ _reduce_node_type(op); // Test for safety anyway
+ // TODO: Make this a warning
+ /*_set_error("Standalone expression, nothing is done in this line.", statement->line);
+ return; */
+ }
+ }
+ } break;
+ case Node::TYPE_CONTROL_FLOW: {
+ ControlFlowNode *cf = static_cast<ControlFlowNode *>(statement);
+ _mark_line_as_safe(cf->line);
+
+ switch (cf->cf_type) {
+ case ControlFlowNode::CF_RETURN: {
+ DataType function_type = current_function->get_datatype();
+
+ DataType ret_type;
+ if (cf->arguments.size() > 0) {
+ ret_type = _reduce_node_type(cf->arguments[0]);
+ if (error_set) {
+ return;
+ }
+ }
+
+ if (!function_type.has_type) break;
+
+ if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
+ // Return void, should not have arguments
+ if (cf->arguments.size() > 0) {
+ _set_error("Void function cannot return a value.", cf->line, cf->column);
+ return;
+ }
+ } else {
+ // Return something, cannot be empty
+ if (cf->arguments.size() == 0) {
+ _set_error("Non-void function must return a value.", cf->line, cf->column);
+ return;
+ }
+
+ if (!_is_type_compatible(function_type, ret_type)) {
+ _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
+ function_type.to_string() + ").",
+ cf->line, cf->column);
+ return;
+ }
+ }
+ } break;
+ case ControlFlowNode::CF_MATCH: {
+ MatchNode *match_node = cf->match;
+ _transform_match_statment(match_node);
+ } break;
+ default: {
+ if (cf->body_else) {
+ _mark_line_as_safe(cf->body_else->line);
+ }
+ for (int i = 0; i < cf->arguments.size(); i++) {
+ _reduce_node_type(cf->arguments[i]);
+ }
+ } break;
+ }
+ } break;
+ case Node::TYPE_CONSTANT: {
+ ConstantNode *cn = static_cast<ConstantNode *>(statement);
+ // Strings are fine since they can be multiline comments
+ if (cn->value.get_type() == Variant::STRING) {
+ break;
+ }
+ } // falthrough
+ default: {
+ _mark_line_as_safe(statement->line);
+ _reduce_node_type(statement); // Test for safety anyway
+ // TODO: Make this a warning
+ /* _set_error("Standalone expression, nothing is done in this line.", statement->line);
+ return; */
+ }
+ }
+ }
+
+ // Parse sub blocks
+ for (int i = 0; i < p_block->sub_blocks.size(); i++) {
+ current_block = p_block->sub_blocks[i];
+ _check_block_types(current_block);
+ current_block = p_block;
+ if (error_set) return;
+ }
+}
+
void GDScriptParser::_set_error(const String &p_error, int p_line, int p_column) {
if (error_set)
@@ -4440,10 +7591,39 @@ Error GDScriptParser::_parse(const String &p_base_path) {
_set_error("Parse Error: " + tokenizer->get_token_error());
}
+ if (error_set && !for_completion) {
+ return ERR_PARSE_ERROR;
+ }
+
+ _determine_inheritance(main_class);
+
+ if (error_set) {
+ return ERR_PARSE_ERROR;
+ }
+
+ current_class = main_class;
+ current_function = NULL;
+ current_block = NULL;
+#ifdef DEBUG_ENABLED
+ if (for_completion) check_types = false;
+#else
+ check_types = false;
+#endif
+
+ // Resolve all class-level stuff before getting into function blocks
+ _check_class_level_types(main_class);
+
if (error_set) {
+ return ERR_PARSE_ERROR;
+ }
+ // Resolve the function blocks
+ _check_class_blocks_types(main_class);
+
+ if (error_set) {
return ERR_PARSE_ERROR;
}
+
return OK;
}
@@ -4461,7 +7641,7 @@ Error GDScriptParser::parse_bytecode(const Vector<uint8_t> &p_bytecode, const St
return ret;
}
-Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion) {
+Error GDScriptParser::parse(const String &p_code, const String &p_base_path, bool p_just_validate, const String &p_self_path, bool p_for_completion, Set<int> *r_safe_lines) {
clear();
@@ -4471,6 +7651,9 @@ Error GDScriptParser::parse(const String &p_code, const String &p_base_path, boo
validating = p_just_validate;
for_completion = p_for_completion;
+#ifdef DEBUG_ENABLED
+ safe_lines = r_safe_lines;
+#endif // DEBUG_ENABLED
tokenizer = tt;
Error ret = _parse(p_base_path);
memdelete(tt);
@@ -4523,7 +7706,11 @@ void GDScriptParser::clear() {
pending_newline = -1;
parenthesis = 0;
current_export.type = Variant::NIL;
+ check_types = true;
error = "";
+#ifdef DEBUG_ENABLED
+ safe_lines = NULL;
+#endif // DEBUG_ENABLED
}
GDScriptParser::CompletionType GDScriptParser::get_completion_type() {