diff options
author | George Marques <george@gmarqu.es> | 2018-05-29 23:16:54 -0300 |
---|---|---|
committer | George Marques <george@gmarqu.es> | 2018-07-20 21:55:16 -0300 |
commit | 743053734f187c220250d88e4e475b7a87767cbc (patch) | |
tree | 414a571036bf8742cb0d04a68063a84c73eded5b /modules/gdscript/gdscript_parser.cpp | |
parent | f7793fc5c9b43b35c5ef1c53e02a8c251a8ba4a4 (diff) | |
download | redot-engine-743053734f187c220250d88e4e475b7a87767cbc.tar.gz |
Add static type checks in the parser
- Resolve types for all identifiers.
- Error when identifier is not found.
- Match return type and error when not returning a value when it should.
- Check unreachable code (code after sure return).
- Match argument count and types for function calls.
- Determine if return type of function call matches the assignment.
- Do static type check with match statement when possible.
- Use type hints to determine export type.
- Check compatibility between type hint and explicit export type.
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 3003 |
1 files changed, 2800 insertions, 203 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 8cd21f026c..cfabb4812d 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" @@ -340,6 +344,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s ConstantNode *nodepath = alloc_node<ConstantNode>(); nodepath->value = NodePath(StringName(path)); + nodepath->datatype = _type_from_variant(nodepath->value); op->arguments.push_back(nodepath); expr = op; @@ -353,6 +358,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 +366,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 +374,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 +382,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 +390,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 +429,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 +488,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 +639,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 +717,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 +783,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 +957,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; @@ -1358,11 +1414,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>(); @@ -1470,13 +1526,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; } @@ -1510,6 +1566,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; } @@ -1611,6 +1668,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) { @@ -1640,24 +1698,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; @@ -1678,13 +1721,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: @@ -1731,6 +1775,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) \ @@ -1744,6 +1789,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) { @@ -1819,6 +1865,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); } } @@ -1925,6 +1978,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 @@ -2058,18 +2120,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()) { @@ -2077,54 +2155,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 *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_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 *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); + + 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); + } - // comare the actual values + // 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: { @@ -2149,22 +2249,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 *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); + 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"; + 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); + 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>(); @@ -2183,12 +2293,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++) { @@ -2228,22 +2342,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 *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); + 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"; + 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); + 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>(); @@ -2262,12 +2386,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()) { @@ -2328,9 +2456,14 @@ 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->datatype = _reduce_node_type(p_match_statement->val_to_match); + + if (error_set) { + return; + } for (int i = 0; i < p_match_statement->branches.size(); i++) { @@ -2345,9 +2478,13 @@ void GDScriptParser::_transform_match_statment(BlockNode *p_block, MatchNode *p_ PatternNode *pattern = branch->patterns[j]; 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; @@ -2355,6 +2492,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; @@ -2370,12 +2515,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; @@ -2433,7 +2585,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: { @@ -2473,6 +2639,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."); @@ -2490,20 +2657,17 @@ 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; @@ -2526,26 +2690,33 @@ 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(); + } 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; OperatorNode *op = alloc_node<OperatorNode>(); op->op = OperatorNode::OP_ASSIGN; op->arguments.push_back(id); op->arguments.push_back(assigned); p_block->statements.push_back(op); + lv->assign_op = op; + lv->assign = assigned; if (!_end_statement()) { _set_error("Expected end of statement (var)"); @@ -2590,6 +2761,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()) @@ -2646,6 +2820,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) { @@ -2669,12 +2845,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: { @@ -2707,6 +2890,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: { @@ -2738,6 +2922,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); @@ -2772,6 +2959,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>(); @@ -2793,6 +2981,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; } } @@ -2816,15 +3008,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: { @@ -2877,6 +3074,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) { return; } } + p_block->has_return = true; } break; case GDScriptTokenizer::TK_CF_MATCH: { @@ -2909,12 +3107,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(); @@ -2965,16 +3165,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; - */ } } } @@ -3158,6 +3348,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } + if (ClassDB::class_exists(p_class->name)) { + _set_error("Class '" + p_class->name + "' shadows a native class."); + return; + } + tokenizer->advance(2); } break; @@ -3187,7 +3382,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { tokenizer->advance(2); // Check if name is shadowing something else - if (ClassDB::class_exists(name)) { + if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) { _set_error("Class '" + String(name) + "' shadows a native class."); return; } @@ -3195,15 +3390,20 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name)); return; } - if (class_map.has(name)) { - _set_error("Class '" + String(name) + "' shadows another class in the file."); - return; - } - for (int i = 0; i < p_class->constant_expressions.size(); i++) { - if (p_class->constant_expressions[i].identifier == name) { - _set_error("Class '" + String(name) + "' shadows a constant of the outer class."); + 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>(); @@ -3215,7 +3415,6 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { newclass->owner = p_class; p_class->subclasses.push_back(newclass); - class_map.insert(name, newclass); if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_EXTENDS) { @@ -3387,6 +3586,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>(); @@ -3401,6 +3605,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(); @@ -4172,6 +4377,20 @@ 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; @@ -4214,42 +4433,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 @@ -4283,15 +4491,36 @@ 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.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(); @@ -4328,7 +4557,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; @@ -4339,7 +4568,22 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { return; } - constant.identifier = tokenizer->get_token_literal(); + StringName const_id = tokenizer->get_token_literal(); + + 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) { @@ -4372,7 +4616,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } 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)", line); @@ -4418,7 +4662,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(); @@ -4453,6 +4697,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { last_assign = last_assign + 1; ConstantNode *cn = alloc_node<ConstantNode>(); cn->value = last_assign; + cn->datatype = _type_from_variant(cn->value); constant.expression = cn; } @@ -4462,28 +4707,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; } constant.type.has_type = true; constant.type.kind = DataType::BUILTIN; constant.type.builtin_type = Variant::INT; - p_class->constant_expressions.push_back(constant); + 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.has_type = true; - cn->datatype.kind = DataType::BUILTIN; - cn->datatype.builtin_type = Variant::DICTIONARY; - p_class->constant_expressions.push_back(enum_constant); + cn->datatype = _type_from_variant(cn->value); + enum_constant.expression = cn; enum_constant.type = cn->datatype; - p_class->constant_expressions.push_back(enum_constant); + p_class->constant_expressions.insert(enum_name, enum_constant); } if (!_end_statement()) { @@ -4619,24 +4861,20 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) { if (base_class) break; if (found) continue; - for (int i = 0; i < p->constant_expressions.size(); i++) { - if (p->constant_expressions[i].identifier == base) { - if (!p->constant_expressions[i].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[i].expression); - base_script = cn->value; - if (base_script.is_null()) { - _set_error("Constant is not a class: " + base, p_class->line); - return; - } - found = true; + 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; } - if (found) break; - p = p->owner; } @@ -4721,9 +4959,25 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) { 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) { @@ -4733,14 +4987,22 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { r_type.builtin_type = Variant::NIL; } break; case GDScriptTokenizer::TK_BUILT_IN_TYPE: { - r_type.kind = DataType::BUILTIN; 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: { - StringName id = tokenizer->get_token_identifier(); - if (ClassDB::class_exists(id)) { + 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; - r_type.native_type = id; + } else { + r_type.kind = DataType::UNRESOLVED; + can_index = true; + full_name = r_type.native_type; } } break; default: { @@ -4749,7 +5011,2320 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) { } 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); + } + + 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(); + } + } + } + + 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) { + 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); + } + } + 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[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) { + 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 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[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)) { + 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 '" + String(callee_name) + "()' is not declared in 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); + } + + String base_name; + if (p_call->arguments[0]->type == Node::TYPE_SELF) { + base_name = current_class->name == StringName() ? "self" : current_class->name; + } else { + base_name = original_type.to_string(); + } + if (!valid) { + 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); + return DataType(); + } + + 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(); + } + } 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 (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) { + _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(); + } + } + + 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) { + // Possibly this is a global, check first + + 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; + } + 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; + } + } + } + } + + // Nothing found, keep looking in local scope + + 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 = *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 (!check_types) { + return DataType(); + } + + if (!p_base_type) { + _set_error("Identifier '" + String(p_identifier) + "' is not declared in the current scope.", p_line); + } else { + _set_error("Identifier '" + String(p_identifier) + "' is not declared in base '" + p_base_type->to_string() + "'.", p_line); + } + return DataType(); +} + +void GDScriptParser::_check_class_level_types(ClassNode *p_class) { + + // Constants + for (Map<StringName, ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) { + ClassNode::Constant &c = E->get(); + 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[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; + } + + if (!v.data_type.has_type) continue; + + 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 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[1] = convert_call; + } + } 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._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++) { + + // Resolve types + p_function->argument_types[i] = _resolve_type(p_function->argument_types[i], p_function->line); + + if (i >= defaults_ofs) { + 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 (!_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 + 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; + valid = valid && p_function->default_values.size() >= default_arg_count; + valid = valid && arg_types.size() == p_function->arguments.size(); + 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) { + _set_error("Function signature doesn't match the parent.", p_function->line); + return; + } + } + } 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; + _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; + _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) { + + // 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); + + if (lv->assign) { + DataType assign_type = _reduce_node_type(lv->assign); + if (!_is_type_compatible(lv->datatype, assign_type)) { + // 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[1] = convert_call; + } + } + + // 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; + } + + DataType lh_type = _reduce_node_type(op->arguments[0]); + + if (error_set) { + return; + } + + if (!lh_type.has_type) { + break; + } + + 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) { + 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 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[1] = convert_call; + } + } break; + case OperatorNode::OP_CALL: + case OperatorNode::OP_PARENT_CALL: { + _reduce_function_call_type(op); + if (error_set) return; + } break; + default: { + // 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); + + switch (cf->cf_type) { + case ControlFlowNode::CF_RETURN: { + DataType function_type = current_function->get_datatype(); + + 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; + } + + DataType ret_type = _reduce_node_type(cf->arguments[0]); + if (error_set) { + 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; + } + } 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: { + // 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) { @@ -4806,6 +7381,27 @@ Error GDScriptParser::_parse(const String &p_base_path) { return ERR_PARSE_ERROR; } + current_class = main_class; + current_function = NULL; + current_block = NULL; + if (for_completion) check_types = false; + + if (check_types) { + // 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; } @@ -4885,6 +7481,7 @@ void GDScriptParser::clear() { pending_newline = -1; parenthesis = 0; current_export.type = Variant::NIL; + check_types = true; error = ""; } |